You said:
How would you start with this final project preview?
To begin working on your final project on a Traffic Management System using parallel and concurrent programming in Java , I recommend breaking the work down into several clear stages. Here's a suggested way to get started:
Scenario 1: Crossing of streets with 4 intersections.
Control the passage of vehicles according to their order of arrival.
Priority to emergency vehicles.
Avoid collisions (this involves synchronization and mutual exclusion).
Vehicle (normal/emergency)
Intersection
Traffic light
Traffic controller (probably a Threador ExecutorService)
Graphical interface (JavaFX)
ClassVehiculo implements Runnable
Class Interseccionwith synchronization mechanisms (such as ReentrantLock)
Class ControladorDeTrafico(central control that coordinates traffic lights and intersections)
Language: Java 11+
Graphical Interface: JavaFX
Concurrent Programming: Thread , ExecutorService, Semaphore, Lock,Condition
Repository: GitHub (for version control and documentation)
Before moving on to the GUI, you can do a console simulation :
Create one Mainthat creates multiple vehicles ( Thread) arriving at an intersection.
Use a standard structure BlockingQueuefor the arrival queue.
Prioritize emergency vehicles.
Use one Lockper intersection to avoid collisions.
Once the logical behavior has been validated:
Create a scene of a 4-way intersection.
Representar los vehículos como nodos (Rectangle, Circle, etc.).
Usar animaciones para movimiento.
Incluir botones para iniciar la simulación, pausar, y añadir vehículos manualmente.
Hilos que representen vehículos que se mueven de manera autónoma.
Semáforos que cambian cada cierto tiempo, controlados por un hilo separado.
Vehículos que esperan si el semáforo está en rojo, pero los de emergencia lo ignoran si es seguro pasar.
Uso de monitores o locks para asegurar que dos vehículos no crucen al mismo tiempo si hay riesgo de colisión.
Desde el principio, empieza a documentar:
Diseño del sistema (diagrama y texto).
Algoritmos usados (control de concurrencia, reglas de paso, prioridad).
Instrucciones de ejecución.
Capturas de pantalla al avanzar la interfaz.
Hacer commits frecuentes y significativos en GitHub.
Dividir tareas si estás trabajando en pareja (uno en lógica, otro en GUI, por ejemplo).
Aplicar pruebas unitarias simples para los componentes críticos (por ejemplo, manejo de intersecciones).
¿Quieres que te ayude a armar el primer esquema de clases o una simulación básica en consola para iniciar?
Perfecto. Basándonos en el escenario 1 (cruce de calles con 4 intersecciones y semáforos), y priorizando la concurrencia, te propongo un primer diseño de clases en Java con responsabilidades claras y bien divididas:
Vehiculojavapublic class Vehiculo implements Runnable {
private final String id;
private final boolean esEmergencia;
private final Interseccion destino;
public Vehiculo(String id, boolean esEmergencia, Interseccion destino) {
this.id = id;
this.esEmergencia = esEmergencia;
this.destino = destino;
}
@Override
public void run() {
destino.intentarCruzar(this);
}
public boolean esEmergencia() { return esEmergencia; }
public String getId() { return id; }
}
Interseccionjavaimport java.util.concurrent.locks.*;
public class Interseccion {
private final String nombre;
private final Lock lock = new ReentrantLock(true);
private final Condition puedeCruzar = lock.newCondition();
public Interseccion(String nombre) {
this.nombre = nombre;
}
public void intentarCruzar(Vehiculo v) {
lock.lock();
try {
while (!esSeguroCruzar(v)) {
puedeCruzar.await();
}
System.out.println(v.getId() + " cruzando " + nombre);
Thread.sleep(1000); // Simula tiempo de cruce
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
puedeCruzar.signalAll();
lock.unlock();
}
}
private boolean esSeguroCruzar(Vehiculo v) {
// Lógica de prioridad para vehículos de emergencia, etc.
return true; // Simplificado
}
}
Semaforojavapublic class Semaforo implements Runnable {
private boolean enVerde = true;
public void run() {
while (true) {
try {
Thread.sleep(5000);
cambiarEstado();
} catch (InterruptedException e) {
break;
}
}
}
public synchronized boolean estaEnVerde() {
return enVerde;
}
private synchronized void cambiarEstado() {
enVerde = !enVerde;
System.out.println("Semáforo cambió a " + (enVerde ? "VERDE" : "ROJO"));
}
}
ControladorDeTraficojavaimport java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ControladorDeTrafico {
private final ExecutorService ejecutor = Executors.newCachedThreadPool();
public void simular() {
Interseccion interseccion1 = new Interseccion("Norte-Sur");
for (int i = 0; i < 10; i++) {
boolean emergencia = i % 4 == 0;
Vehiculo v = new Vehiculo("Vehículo " + i, emergencia, interseccion1);
ejecutor.execute(v);
}
}
}
Mainjavapublic class Main {
public static void main(String[] args) {
ControladorDeTrafico controlador = new ControladorDeTrafico();
controlador.simular();
}
}
sql+----------------+
| Vehiculo |
+----------------+
| - id |
| - esEmergencia |
| - destino |
+----------------+
| + run() |
+----------------+
+----------------+
| Interseccion |
+----------------+
| - lock |
| - condition |
+----------------+
| + intentarCruzar() |
| - esSeguroCruzar() |
+----------------+
+----------------+
| Semaforo |
+----------------+
| - enVerde |
+----------------+
| + run() |
| + estaEnVerde()|
| - cambiarEstado() |
+----------------+
+------------------------+
| ControladorDeTrafico |
+------------------------+
| - ejecutor |
+------------------------+
| + simular() |
+------------------------+
Puedes empezar con una simulación básica sin JavaFX.
Luego agregar lógica para el semáforo y prioridad real para emergencias.
Finalmente integrar JavaFX para visualización y control manual.
¿Quieres que te prepare este proyecto en un esqueleto listo para compilar? ¿O quieres que diseñemos la versión con JavaFX de inmediato?
Excelente observación y muy lógica tu línea de pensamiento. Vamos a desglosarlo:
Sí, si vas a mantener una cola o lista de vehículos en espera por intersección, entonces ControladorDeTrafico o incluso Interseccion necesita llevar esa lista para decidir el orden de paso.
Sin embargo, para simular la prioridad por orden de llegada, no deberías depender del id del vehículo, sino de cuándo fue insertado en la cola.
Si lo manejas como:
javaQueue<Vehiculo> cola = new LinkedList<>();
O mejor:
javaBlockingQueue<Vehiculo> cola = new LinkedBlockingQueue<>();
Entonces el orden de llegada natural te lo da la cola.
Pero si quieres controlar explícitamente la prioridad (por ejemplo: vehículos de emergencia, y luego entre los normales el más antiguo), una PriorityQueue con un Comparator personalizado es lo ideal.
Vehiculo con prioridad dinámicajavapublic class Vehiculo implements Comparable<Vehiculo> {
private final String id;
private final boolean esEmergencia;
private final long timestamp = System.currentTimeMillis(); // Llegada
public Vehiculo(String id, boolean esEmergencia) {
this.id = id;
this.esEmergencia = esEmergencia;
}
public boolean esEmergencia() { return esEmergencia; }
public String getId() { return id; }
public long getTimestamp() { return timestamp; }
@Override
public int compareTo(Vehiculo otro) {
// Emergencia primero, luego por orden de llegada
if (this.esEmergencia && !otro.esEmergencia) return -1;
if (!this.esEmergencia && otro.esEmergencia) return 1;
return Long.compare(this.timestamp, otro.timestamp);
}
}
Interseccion con PriorityQueuejavaimport java.util.PriorityQueue;
import java.util.Queue;
public class Interseccion {
private final Queue<Vehiculo> colaVehiculos = new PriorityQueue<>();
public synchronized void agregarVehiculo(Vehiculo v) {
colaVehiculos.offer(v);
notifyAll();
}
public synchronized void procesarVehiculos() {
new Thread(() -> {
while (true) {
try {
while (colaVehiculos.isEmpty()) wait();
Vehiculo v = colaVehiculos.poll();
System.out.println(v.getId() + " está cruzando");
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
}).start();
}
}
ControladorDeTraficojavapublic class ControladorDeTrafico {
private final Interseccion interseccion = new Interseccion();
public void simular() {
interseccion.procesarVehiculos();
for (int i = 0; i < 10; i++) {
Vehiculo v = new Vehiculo("Vehículo " + i, i % 3 == 0);
interseccion.agregarVehiculo(v);
try {
Thread.sleep(500); // Simula llegada escalonada
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
No uses id para orden de llegada. Aunque parezca que un Vehículo 0 llegó antes que Vehículo 9, puede no ser cierto si fueron instanciados en hilos con demoras.
El atributo prioridad que mencionaste podría quedar implícito por esEmergencia, y el orden por llegada lo maneja el timestamp.
PriorityQueue + Comparable te da esa lógica automáticamente.
¿Quieres que combine esto con una interfaz JavaFX, o prefieres primero probarlo por consola? También puedo ayudarte a modularizar más si piensas agregar múltiples intersecciones.
Claro, te explico cómo funciona cada clase en la propuesta mejorada del sistema de gestión de tráfico, con foco en el escenario 1 (cruce de calles con prioridad y concurrencia). Vamos una por una:
VehiculoRepresenta un vehículo que llega a una intersección. Puede ser normal o de emergencia.
javaprivate final String id; // Nombre o identificador del vehículo
private final boolean esEmergencia; // Indica si es vehículo de emergencia
private final long timestamp; // Marca el momento en que fue creado (para orden de llegada)
Implementa Comparable<Vehiculo> para que se pueda ordenar en una PriorityQueue.
Si es de emergencia, se le da prioridad sobre los demás.
Si ambos son normales, se ordenan por orden de llegada (timestamp).
java@Override
public int compareTo(Vehiculo otro) {
if (this.esEmergencia && !otro.esEmergencia) return -1;
if (!this.esEmergencia && otro.esEmergencia) return 1;
return Long.compare(this.timestamp, otro.timestamp);
}
InterseccionControla la lógica de paso de vehículos por la intersección. Asegura que solo uno pase a la vez, y en orden de prioridad.
javaprivate final Queue<Vehiculo> colaVehiculos = new PriorityQueue<>();
Aquí se almacenan los vehículos esperando cruzar. La cola está ordenada por:
Vehículos de emergencia primero.
Luego por orden de llegada (timestamp).
agregarVehiculo(Vehiculo v)Añade un vehículo a la cola.
Usa synchronized para proteger el acceso concurrente.
Llama a notifyAll() por si el hilo de cruce estaba esperando.
javapublic synchronized void agregarVehiculo(Vehiculo v) {
colaVehiculos.offer(v);
notifyAll();
}
procesarVehiculos()Inicia un hilo separado que constantemente atiende la cola.
Cada vez que encuentra un vehículo, lo “cruza” (simulado con sleep).
Si la cola está vacía, el hilo espera (wait()).
javapublic synchronized void procesarVehiculos() {
new Thread(() -> {
while (true) {
try {
while (colaVehiculos.isEmpty()) wait();
Vehiculo v = colaVehiculos.poll();
System.out.println(v.getId() + " está cruzando");
Thread.sleep(1000); // Tiempo simulado de cruce
} catch (InterruptedException e) {
break;
}
}
}).start();
}
Este método simula el semáforo humano o automático que decide quién puede cruzar.
ControladorDeTraficoEs el "orquestador" del sistema. Se encarga de:
Crear vehículos.
Mandarlos a una intersección.
Lanzar la simulación de paso en la intersección.
simular()Llama a procesarVehiculos() en la intersección (para iniciar el hilo).
Crea una serie de vehículos (algunos de emergencia).
Los añade uno a uno a la intersección con un pequeño Thread.sleep() para simular llegadas escalonadas.
javapublic void simular() {
interseccion.procesarVehiculos();
for (int i = 0; i < 10; i++) {
Vehiculo v = new Vehiculo("Vehículo " + i, i % 3 == 0); // Cada 3 es emergencia
interseccion.agregarVehiculo(v);
Thread.sleep(500); // Simula el tiempo entre llegadas
}
}
MainPunto de entrada a la aplicación. Llama al ControladorDeTrafico y arranca la simulación.
javapublic class Main {
public static void main(String[] args) {
ControladorDeTrafico controlador = new ControladorDeTrafico();
controlador.simular();
}
}
Se crea una Interseccion que sabe cómo manejar vehículos concurrentemente.
El ControladorDeTrafico le inyecta vehículos (con prioridad si aplica).
La Interseccion los atiende en un hilo separado, usando una cola con prioridad.
Los vehículos cruzan de uno en uno, según la prioridad (emergencia + llegada).
¿Te gustaría que integre un Semáforo también, o que prepare una versión multintersección? También puedo ayudarte a hacer una versión con JavaFX para visualizar el cruce.
Perfecto Jean. Ya que estás siguiendo una planificación tan detallada y profesional, aquí te estructuro el proyecto conforme a esa planificación y ajustando lo que discutimos antes (como el orden de llegada, las prioridades, los managers, y el modelo compartido con JavaFX).
csssrc/
└── main/
├── java/
│ └── com/
│ └── trafico/
│ ├── model/ → API compartida
│ ├── simulation/ → Lógica del backend y concurrencia (Carlos)
│ ├── controller/ → Lógica de UI (Jean)
│ ├── view/ → FXML y clases de vista (Jean)
│ └── util/ → Enums, logs, helpers
└── resources/
├── config/ → Archivos JSON para escenarios
├── fxml/ → main.fxml, escenarios
└── logback.xml
com.trafico.model)enum TipoVehiculo { NORMAL, EMERGENCIA }
record VehiculoState(String id, double posX, double posY, TipoVehiculo tipo)
interface SimulationObserver { void onStateChange(Map<String, VehiculoState> snapshot); }
class SimulationModel
Atributo:
javaprivate final ObservableMap<String, VehiculoState> vehiculos =
FXCollections.observableMap(new ConcurrentHashMap<>());
Métodos: addState(), updateState(), removeState(), notificación a observadores.
com.trafico.simulation)abstract class Vehiculo implements Runnable
Atributos: posX, posY, velocidad, id, TipoVehiculo tipo, SimulationModel model
Método run() publica estado y pregunta al Manager si puede avanzar.
class VehiculoNormal extends Vehiculo {}
class VehiculoEmergencia extends Vehiculo {}
class InterseccionManager
Maneja un Semaphore(1, true)
Método: acquire() y release() para paso de vehículos.
class CruceManager
Agrupa 4 InterseccionManager para las 4 esquinas.
class SemaforoManager (para escenario 2)
Usa ReentrantLock y Condition para manejar hilos esperando luz verde.
class ConfigLoader → lee JSON de /resources/config/escenario1.json
class SimulationFactory → crea vehículos desde config
com.trafico.controller y com.trafico.view)MainController.java
Métodos: handlePlay(), handlePause(), handleReset()
Maneja estados y vinculación con SimulationViewModel
SimulationViewModel.java
Observa el SimulationModel
Exposición de ObservableList<Node> para renderizado
FXML
main.fxml → MenuBar + Controles + StackPane (canvas)
Lienzo es un Pane con coordenadas absolutas
AnimationTimer
Se ejecuta 60 FPS, actualiza la posición de cada Node en pantalla
El uso de Semaphore(1, true) en InterseccionManager ya resuelve la lógica de “primero en llegar, primero en salir” sin necesidad de comparar IDs ni timestamps manualmente.
Los vehículos publican su estado al SimulationModel, y este lo notifica a la vista.
El modelo compartido elimina los errores por acceso concurrente entre lógica y UI.
La lógica de prioridad para emergencias está en la subclase VehiculoEmergencia y en el Manager que le da prioridad (por ejemplo, puede usar un segundo semáforo o interruptor).
Comienza el diseño del main.fxml y su controlador.
Implementa el SimulationViewModel y AnimationTimer.
Crea el README.md con estructura profesional (puedo ayudarte con una plantilla si quieres).
Inicia con SimulationModel, Vehiculo, InterseccionManager y la lógica de concurrencia para el cruce.
Prueba con CountDownLatch para validar orden y correcto cruce de 10 vehículos.
¿Quieres que genere los archivos base para estas clases (como un esqueleto) y te los agrupe por paquete para que puedas empezar directo desde IntelliJ o Eclipse?
Perfecto Jean, como tú estás encargado del frontend y documentación, te dejo una guía clara y paso a paso para comenzar tu parte del proyecto de acuerdo a la planificación.
Java 21 (Temurin o similar)
JavaFX SDK (añadido al proyecto vía Gradle con el plugin org.openjfx.javafxplugin)
Verifica que puedes correr un HelloWorld.javafx
Ruta sugerida: src/main/resources/fxml/main.fxml
Contenido mínimo (puedes expandirlo luego):
xml<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<BorderPane xmlns:fx="http://javafx.com/fxml" fx:controller="com.trafico.controller.MainController">
<top>
<MenuBar>
<Menu text="Archivo">
<MenuItem text="Salir" onAction="#handleExit"/>
</Menu>
</MenuBar>
</top>
<center>
<StackPane fx:id="lienzoSimulacion"/>
</center>
<bottom>
<HBox spacing="10" alignment="center">
<Button text="Iniciar" onAction="#handlePlay"/>
<Button text="Pausar" onAction="#handlePause"/>
<Button text="Resetear" onAction="#handleReset"/>
</HBox>
</bottom>
</BorderPane>
Ubicación: com.trafico.controller.MainController
java@FXML private StackPane lienzoSimulacion;
@FXML
private void handlePlay() {
// iniciar simulación
}
@FXML
private void handlePause() {
// pausar simulación
}
@FXML
private void handleReset() {
// resetear
}
@FXML
private void handleExit() {
Platform.exit();
}
Conecta el SimulationModel y SimulationViewModel.
Añade los nodos (Circle, Rectangle) al lienzoSimulacion.
Muestra alertas con Platform.runLater() si es necesario (ej: vehículo de emergencia).
Clase sugerida: SimulationViewModel.java
javapublic class SimulationViewModel {
private final Map<String, Node> nodosVehiculos = new HashMap<>();
private final Pane lienzo;
public SimulationViewModel(SimulationModel model, Pane lienzo) {
this.lienzo = lienzo;
model.addObserver(snapshot -> Platform.runLater(() -> render(snapshot)));
}
private void render(Map<String, VehiculoState> snapshot) {
for (var entry : snapshot.entrySet()) {
String id = entry.getKey();
VehiculoState state = entry.getValue();
Node nodo = nodosVehiculos.get(id);
if (nodo == null) {
nodo = new Circle(5, state.tipo() == TipoVehiculo.EMERGENCIA ? Color.RED : Color.BLUE);
nodosVehiculos.put(id, nodo);
lienzo.getChildren().add(nodo);
}
nodo.setTranslateX(state.posX());
nodo.setTranslateY(state.posY());
}
}
}
Añade un AnimationTimer en el MainController que llame a este renderizado.
Deja que el SimulationModel notifique con los estados actualizados.
README.md en GitHubEstructura:
md# Simulador de Tráfico
Simulación de tráfico en un cruce de calles y una autopista con JavaFX y concurrencia.
## Requisitos
- Java 21 (Temurin)
- JavaFX SDK
- Gradle
## Ejecución
```bash
./gradlew run
yaml
#### 4.2. `docs/SETUP.md`
Incluye:
- Cómo instalar Java 21 y JavaFX
- Cómo configurar JavaFX en IntelliJ
- Cómo compilar y correr desde terminal
---
## 🧠 ¿Y ahora?
Empieza por esto:
1. Crea `main.fxml` y `MainController.java`.
2. Prueba que puedes cargar la vista (`Application.start()` con `FXMLLoader`).
3. Integra el `SimulationModel` y el `ViewModel`.
4. Coordina con Carlos para tener un flujo mínimo de `VehiculoState` que puedas renderizar.
5. Documenta todo lo que vayas montando.
---
¿Quieres que te prepare una plantilla de proyecto con Gradle + JavaFX + FXML ya configurada para que empieces más rápido?
El código que presentas tiene el objetivo de aplicar TF-IDF a una columna llamada 'documentos_tokens' de un DataFrame df. Sin embargo, hay un problema común dependiendo del formato de los datos en esa columna.
TfidfVectorizer espera que cada documento sea una cadena de texto completa (no listas de tokens).
Si df['documentos_tokens'] ya contiene listas de tokens, debes convertirlas de nuevo a texto.
Si tienes esto en tu DataFrame:
pythondf['documentos_tokens']
# Ejemplo:
# 0 ['hola', 'mundo']
# 1 ['python', 'es', 'genial']
Entonces, antes de pasar eso al TfidfVectorizer, une los tokens:
pythondf['documentos_str'] = df['documentos_tokens'].apply(lambda tokens: ' '.join(tokens))
Luego usa el TfidfVectorizer así:
pythonfrom sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(df['documentos_str'])
x_array = X.toarray()
print(x_array[0])
documentos_tokens ya son stringsSi en cambio ya tienes strings así:
python"hola mundo"
"python es genial"
entonces el problema puede venir de otra parte (ej. datos NaN, strings vacíos, etc.). En ese caso, asegúrate con:
pythondf['documentos_tokens'] = df['documentos_tokens'].fillna('')
¿Quieres que te ayude a revisar el contenido de esa columna o mostrar cómo graficar el resultado de TF-IDF?
Perfecto, si df['documentos_tokens'] ya contiene listas de tokens como:
python['hola', 'mundo']
['python', 'es', 'genial']
entonces el problema es que TfidfVectorizer no puede trabajar directamente con listas, sino con strings de texto plano (una cadena por documento).
Convierte cada lista de tokens en una cadena antes de aplicar TfidfVectorizer:
pythondf['documentos_str'] = df['documentos_tokens'].apply(lambda tokens: ' '.join(tokens))
Luego, usa esta nueva columna:
pythonfrom sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(df['documentos_str'])
x_array = X.toarray()
print(x_array[0]) # Vector TF-IDF del primer documento
Asegúrate de que no hay NaN o listas vacías:
pythondf = df[df['documentos_tokens'].notnull()]
df = df[df['documentos_tokens'].apply(len) > 0]
Si quieres ver los términos:
pythonprint(vectorizer.get_feature_names_out())
Para ver qué término corresponde a qué índice del vector:
pythonimport pandas as pd
tfidf_df = pd.DataFrame(x_array, columns=vectorizer.get_feature_names_out())
print(tfidf_df.head())
¿Quieres que te ayude a visualizar los vectores TF-IDF con un gráfico o a seleccionar las palabras más importantes por documento?
Gracias por tu paciencia. Ya revisé el notebook y aquí está lo que sucede:
Tomas la columna 'text', la limpias, la tokenizas, y le aplicas stopwords, stemming y lemmatization.
Luego creas una nueva columna documentos_tokens con los tokens unidos como string:
pythondocumentos_tokens = [' '.join(tokens) for tokens in tokensdf]
🔁 Esto es correcto y es justo lo que TfidfVectorizer y CountVectorizer esperan: una lista de cadenas de texto.
Cuando imprimes:
pythonprint(x_array[0])
Te da algo como:
python[0. 0. 0. ... 0. 0. 0.]
Esto es normal, porque:
Estás generando un vector de dimensión igual al vocabulario total (miles de palabras).
Para un documento individual, solo unas pocas palabras están presentes, por eso hay tantos ceros.
get_feature_names_out() es un arreglo largopython['aa' 'aaaahs' 'aaahed' ... 'zone' 'zoolander' 'zoom']
Esto es simplemente el vocabulario ordenado, nada malo.
pythonimport pandas as pd
tfidf_df = pd.DataFrame(x_array, columns=vectorizer.get_feature_names_out())
print(tfidf_df.head()) # Muestra los primeros documentos y sus pesos
pythonconsulta = "stylish dress"
consulta_vector = vectorizer.transform([consulta]).toarray()
Y luego puedes calcular similaridad del coseno:
pythonfrom sklearn.metrics.pairwise import cosine_similarity
similitudes = cosine_similarity(consulta_vector, x_array)
indices = similitudes[0].argsort()[::-1][:5] # top 5 documentos más similares
print(df.iloc[indices][['text']])
Tu flujo está correcto:
Los vectores TF-IDF son correctos aunque parezcan "vacíos".
documentos_tokens ya tiene strings, así que no hay error de tipo.
Puedes seguir con el paso 3 del enunciado: hacer consultas con TF-IDF.
Luego aplica cosine similarity para construir tu Vector Space Model.
¿Quieres que te prepare una celda con todo eso listo (consulta + similitud + top 5 textos)?
Perfecto, volvamos al proyecto del Sistema de Gestión de Tráfico.
Ya tenemos:
📌 Planificación detallada (roles, paquetes, flujo de trabajo en Git).
🧠 Diseño de clases centrales (vehículo, managers, modelo compartido).
✅ Estructura del frontend que tú, Jean, manejarás (JavaFX + FXML + AnimationTimer).
💡 Coordinación clara entre backend (Carlos) y frontend (Jean) vía SimulationModel.
Te propongo comenzar con esto:
bashsrc/
├── main/
│ ├── java/
│ │ └── com/trafico/
│ │ ├── model/ # API compartida (SimulationModel, VehiculoState)
│ │ ├── controller/ # SimulationController (MainController)
│ │ └── viewmodel/ # SimulationViewModel
│ └── resources/
│ └── fxml/
│ └── main.fxml
main.fxml (interfaz inicial)Ubicación: resources/fxml/main.fxml
xml<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<BorderPane xmlns:fx="http://javafx.com/fxml"
fx:controller="com.trafico.controller.MainController">
<top>
<MenuBar>
<Menu text="Archivo">
<MenuItem text="Salir" onAction="#handleExit"/>
</Menu>
</MenuBar>
</top>
<center>
<Pane fx:id="lienzo" prefWidth="800" prefHeight="600" style="-fx-background-color: lightgray;"/>
</center>
<bottom>
<HBox spacing="10" alignment="center" padding="10">
<Button text="Iniciar" onAction="#handlePlay"/>
<Button text="Pausar" onAction="#handlePause"/>
<Button text="Resetear" onAction="#handleReset"/>
</HBox>
</bottom>
</BorderPane>
MainController.javaUbicación: com.trafico.controller.MainController
javapackage com.trafico.controller;
import javafx.animation.AnimationTimer;
import javafx.fxml.FXML;
import javafx.scene.layout.Pane;
import com.trafico.viewmodel.SimulationViewModel;
import com.trafico.model.SimulationModel;
public class MainController {
@FXML
private Pane lienzo;
private SimulationModel model;
private SimulationViewModel viewModel;
private AnimationTimer timer;
@FXML
public void initialize() {
model = new SimulationModel();
viewModel = new SimulationViewModel(model, lienzo);
timer = new AnimationTimer() {
@Override
public void handle(long now) {
viewModel.render();
}
};
}
@FXML
public void handlePlay() {
timer.start();
}
@FXML
public void handlePause() {
timer.stop();
}
@FXML
public void handleReset() {
lienzo.getChildren().clear();
model.clear(); // Debes agregar este método en SimulationModel
}
@FXML
public void handleExit() {
System.exit(0);
}
}
SimulationViewModel.javaUbicación: com.trafico.viewmodel.SimulationViewModel
javapackage com.trafico.viewmodel;
import com.trafico.model.SimulationModel;
import com.trafico.model.VehiculoState;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import java.util.HashMap;
import java.util.Map;
public class SimulationViewModel {
private final SimulationModel model;
private final Pane lienzo;
private final Map<String, Node> nodosVehiculos = new HashMap<>();
public SimulationViewModel(SimulationModel model, Pane lienzo) {
this.model = model;
this.lienzo = lienzo;
}
public void render() {
for (VehiculoState state : model.getEstados()) {
Node nodo = nodosVehiculos.get(state.id());
if (nodo == null) {
nodo = new Circle(5, state.tipo().equals("EMERGENCIA") ? Color.RED : Color.BLUE);
nodosVehiculos.put(state.id(), nodo);
lienzo.getChildren().add(nodo);
}
nodo.setTranslateX(state.posX());
nodo.setTranslateY(state.posY());
}
}
}
SimulationModel.java y VehiculoState.java (simplificados por ahora)Ubicación: com.trafico.model
VehiculoState.javajavapackage com.trafico.model;
public record VehiculoState(String id, double posX, double posY, String tipo) {}
SimulationModel.javajavapackage com.trafico.model;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SimulationModel {
private final Map<String, VehiculoState> vehiculos = new ConcurrentHashMap<>();
public void updateState(VehiculoState state) {
vehiculos.put(state.id(), state);
}
public Collection<VehiculoState> getEstados() {
return vehiculos.values();
}
public void clear() {
vehiculos.clear();
}
}
Puedes crear un simulador "falso" que inyecte vehículos en SimulationModel para que tú puedas verlos moverse en pantalla antes de conectar con Carlos.
¿Quieres que te dé esa clase de simulador temporal (MockSimulator) para que puedas validar la animación mientras Carlos termina su parte?
Perfecto. Vamos a implementar el Paso 1 y Paso 2: crear el repositorio base (estructural) del proyecto y definir las clases del modelo compartido (VehiculoState y SimulationModel), sin referirnos a nombres como Jean o Carlos. Lo haremos como si fueras tú quien ejecuta todo.
cssproyecto-trafico/
├── build.gradle.kts
├── settings.gradle.kts
├── src/
│ └── main/
│ ├── java/
│ │ └── com/trafico/
│ │ └── model/
│ │ ├── VehiculoState.java
│ │ └── SimulationModel.java
│ └── resources/
└── README.md
build.gradle.ktskotlinplugins {
java
application
id("org.openjfx.javafxplugin") version "0.0.13"
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.openjfx:javafx-controls:21")
implementation("org.openjfx:javafx-fxml:21")
implementation("org.openjfx:javafx-graphics:21")
}
javafx {
version = "21"
modules = listOf("javafx.controls", "javafx.fxml", "javafx.graphics")
}
application {
mainClass.set("com.trafico.Main") // Temporal, cambiaremos más adelante
}
settings.gradle.ktskotlinrootProject.name = "proyecto-trafico"
VehiculoState.java (inmutable con record)Ubicación: src/main/java/com/trafico/model/VehiculoState.java
javapackage com.trafico.model;
public record VehiculoState(String id, double posX, double posY, TipoVehiculo tipo) {}
TipoVehiculo.java (Enum simple)Ubicación: src/main/java/com/trafico/model/TipoVehiculo.java
javapackage com.trafico.model;
public enum TipoVehiculo {
NORMAL,
EMERGENCIA
}
SimulationModel.javaUbicación: src/main/java/com/trafico/model/SimulationModel.java
javapackage com.trafico.model;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import java.util.concurrent.ConcurrentHashMap;
public class SimulationModel {
private final ObservableMap<String, VehiculoState> vehiculos;
public SimulationModel() {
this.vehiculos = FXCollections.observableMap(new ConcurrentHashMap<>());
}
public void updateState(VehiculoState estado) {
vehiculos.put(estado.id(), estado);
}
public void removeState(String id) {
vehiculos.remove(id);
}
public ObservableMap<String, VehiculoState> getVehiculos() {
return vehiculos;
}
public void clear() {
vehiculos.clear();
}
}
Para completar este bloque inicial:
Puedes subir esta estructura a GitHub.
Crear el README.md con nombre del proyecto, instrucciones mínimas y participantes (si aplica).
Añadir en GitHub un tablero Kanban de tipo "Project" y registrar tarjetas como:
Crear modelo compartido
Integrar FXML y JavaFX
Implementar vista del cruce
Implementar vista de autopista
¿Quieres que te genere un .zip con esta estructura lista para abrir en IntelliJ o prefieres que sigamos con el FXML y controlador del escenario 1?
Ese error "Cannot resolve symbol 'javafx'" significa que tu entorno (probablemente IntelliJ o tu compilador) no está reconociendo las dependencias de JavaFX, lo cual es muy común en configuraciones nuevas, especialmente con Gradle + Java 21.
Debes tener estos dos archivos en el root de tu proyecto:
build.gradle.kts
settings.gradle.kts
Y en build.gradle.kts debes tener exactamente esto:
kotlinplugins {
java
application
id("org.openjfx.javafxplugin") version "0.0.13"
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.openjfx:javafx-controls:21")
implementation("org.openjfx:javafx-fxml:21")
implementation("org.openjfx:javafx-graphics:21")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
javafx {
version = "21"
modules = listOf("javafx.controls", "javafx.fxml", "javafx.graphics")
}
application {
mainClass.set("com.trafico.Main") // O el que uses
}
Abre IntelliJ.
Ve al panel lateral de Gradle (barra derecha).
Haz clic en el ícono de 🔄 "Refresh All Projects".
Espera a que Gradle descargue las dependencias.
Asegúrate de que IntelliJ detectó el proyecto como un proyecto Gradle con Kotlin DSL.
Ve a File > Project Structure (Ctrl+Alt+Shift+S).
En "Project":
Project SDK debe estar en Java 21.
Project language level: 21.
En "Modules" > "Dependencies":
Asegúrate de que esté seleccionado el SDK correcto.
JavaFX no se debe agregar manualmente si usas Gradle, solo verifica que Gradle esté sincronizado.
Verifica esto:
¿Tienes conexión a Internet? Gradle necesita descargar las dependencias.
¿No se ha generado el directorio ~/.gradle/caches/...?
Corre este comando en terminal para ver si el build pasa:
bash./gradlew clean build
Una vez sincronizado, puedes importar clases como:
javaimport javafx.application.Application;
import javafx.stage.Stage;
y que no aparezcan subrayadas en rojo.
¿Quieres que te prepare un proyecto JavaFX funcional en ZIP con Gradle 21 para que solo lo abras en IntelliJ y funcione de una vez?
Claro. Vamos a modificar tu build.gradle.kts para que:
Siga usando Java 21.
Incluya soporte para JavaFX (FXML, controls, graphics).
Tenga configurado un mainClass.
Mantenga soporte para JUnit 5 (ya incluido).
build.gradle.kts para JavaFX + JUnit 5kotlinplugins {
id("java")
id("application")
id("org.openjfx.javafxplugin") version "0.0.13"
}
group = "edu.pucmm"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
dependencies {
implementation("org.openjfx:javafx-controls:21")
implementation("org.openjfx:javafx-fxml:21")
implementation("org.openjfx:javafx-graphics:21")
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
}
javafx {
version = "21"
modules = listOf("javafx.controls", "javafx.fxml", "javafx.graphics")
}
application {
// Cambia esta clase por tu clase principal real
mainClass.set("com.trafico.Main")
}
tasks.test {
useJUnitPlatform()
}
Asegúrate de que el archivo settings.gradle.kts contenga:
kotlinrootProject.name = "proyecto-trafico"
Ejecuta:
bash./gradlew clean build
Si estás en IntelliJ, haz clic en "Reload All Gradle Projects" tras guardar los cambios.
¿Quieres que prepare el Main.java base para que puedas probar que todo corre con una ventana JavaFX vacía?
Buena pregunta. Vamos a aclararlo claramente:
VehiculoState?VehiculoState es una clase (o record en Java moderno) que representa el estado visible de un vehículo en la simulación: lo que necesita saber el frontend (la interfaz gráfica) para dibujarlo en pantalla.
Porque en tu simulación:
Los vehículos reales son objetos complejos que viven en hilos y manejan lógica de concurrencia, movimiento, permisos, prioridades, etc.
Pero la interfaz gráfica solo necesita saber:
¿Dónde está el vehículo?
¿Qué tipo es? (emergencia o normal)
¿Cuál es su ID? (para rastrearlo)
Si la UI accediera directamente al objeto Vehiculo (concurrencia), se podría corromper la información. Por eso se crea una versión inmutable y segura para leer: el VehiculoState.
javapublic record VehiculoState(String id, double posX, double posY, TipoVehiculo tipo) {}
Es decir:
id: un identificador único (ej. "Vehiculo-3").
posX, posY: coordenadas para posicionarlo en el lienzo de JavaFX.
tipo: si es NORMAL o EMERGENCIA.
El backend crea y mueve vehículos (Vehiculo).
Cada vez que un vehículo se mueve, genera un VehiculoState y lo pasa al SimulationModel.
El SimulationModel mantiene un Map<String, VehiculoState> con todos los vehículos visibles.
La interfaz gráfica consulta este mapa y dibuja/actualiza cada Circle en pantalla.
| Vehículo (Objeto Runnable) | VehiculoState (Versión visible para la UI) |
|---|---|
| Tiene lógica compleja | Solo tiene posición y tipo |
| Usa hilos, managers, locks | Inmutable y seguro para leer |
| No debe ser accedido por la UI | Hecho para ser leído por la UI |
Separar lógica (backend) de representación (frontend) te permite:
Evitar errores de concurrencia.
Escalar fácilmente (la UI puede observar cambios, sin modificar nada).
Usar patrones modernos como MVVM (Modelo-Vista-ViewModel).
¿Quieres que te muestre un ejemplo donde un vehículo se mueve y publica su VehiculoState al modelo compartido?
¡Exactamente! ✅
Sí tienes que implementar los modelos principales: Vehiculo, Interseccion, y Semaforo (y sus respectivos managers). Son esenciales para que la simulación funcione en el backend.
| Clase | Rol principal |
|---|---|
Vehiculo | Agente en un hilo que se mueve, interactúa con el entorno y actualiza su VehiculoState. |
InterseccionManager | Controla el paso en cruces (con Semaphore(1, true)), permite un vehículo a la vez. |
SemaforoManager | Maneja luces (rojo/verde), controla a vehículos en la autopista con Lock y Condition. |
VehiculoState | Versión inmutable del vehículo para que la UI lo pueda renderizar de forma segura. |
Vehiculo (abstract class)javapublic abstract class Vehiculo implements Runnable {
protected String id;
protected double posX, posY;
protected double velocidad;
protected TipoVehiculo tipo;
protected SimulationModel modelo;
public Vehiculo(...) { ... }
@Override
public void run() {
while (true) {
calcularMovimiento();
if (puedeAvanzar()) {
mover();
modelo.updateState(new VehiculoState(id, posX, posY, tipo));
}
Thread.sleep(16);
}
}
protected abstract void calcularMovimiento();
protected abstract boolean puedeAvanzar();
protected abstract void mover();
}
VehiculoNormal: sigue reglas comunes.
VehiculoEmergencia: puede ignorar semáforos si está despejado, tiene prioridad en intersecciones.
InterseccionManagerjavapublic class InterseccionManager {
private final Semaphore cruce = new Semaphore(1, true); // FIFO justo
public void entrarCruce() throws InterruptedException {
cruce.acquire();
}
public void salirCruce() {
cruce.release();
}
}
SemaforoManagerjavapublic class SemaforoManager {
private final ReentrantLock lock = new ReentrantLock();
private final Condition luzVerde = lock.newCondition();
private boolean enVerde = false;
public void esperarLuzVerde() throws InterruptedException {
lock.lock();
try {
while (!enVerde) {
luzVerde.await();
}
} finally {
lock.unlock();
}
}
public void cambiarLuz(boolean verde) {
lock.lock();
try {
enVerde = verde;
if (verde) luzVerde.signalAll();
} finally {
lock.unlock();
}
}
}
VehiculoState?Cada Vehiculo, al moverse, debe llamar:
javamodelo.updateState(new VehiculoState(id, posX, posY, tipo));
Y ese modelo (SimulationModel) es observado por el frontend para renderizar.
Sí necesitas hacer los modelos Vehiculo, InterseccionManager, SemaforoManager, y usar VehiculoState como puente con la UI.
¿Quieres que empecemos a implementar VehiculoNormal y InterseccionManager juntos como primer paso real de simulación?
Perfecto. Vamos a resumir TODO lo que tienes que hacer en el frontend (JavaFX) y luego cómo puedes probarlo sin tener el backend listo (simulación falsa).
Mostrar visualmente vehículos que se mueven, con una interfaz clara, fluida y desacoplada del backend.
main.fxml: interfaz visual (botones, panel de dibujo).
MainController.java: controlador conectado al FXML.
SimulationViewModel.java: renderiza los datos en pantalla.
SimulationModel.java: contiene un ObservableMap con los VehiculoState.
VehiculoState.java: posición, tipo y ID de cada vehículo.
TipoVehiculo.java: enum con NORMAL, EMERGENCIA.
Crea un archivo llamado main.fxml en resources/fxml/:
xml<BorderPane xmlns:fx="http://javafx.com/fxml" fx:controller="com.trafico.controller.MainController">
<top>
<MenuBar>
<Menu text="Archivo">
<MenuItem text="Salir" onAction="#handleExit"/>
</Menu>
</MenuBar>
</top>
<center>
<Pane fx:id="lienzo" prefWidth="800" prefHeight="600" style="-fx-background-color: lightgray;"/>
</center>
<bottom>
<HBox alignment="center" spacing="10" padding="10">
<Button text="Iniciar" onAction="#handlePlay"/>
<Button text="Pausar" onAction="#handlePause"/>
<Button text="Resetear" onAction="#handleReset"/>
</HBox>
</bottom>
</BorderPane>
java@FXML private Pane lienzo;
private SimulationModel modelo;
private SimulationViewModel vista;
private AnimationTimer timer;
@FXML
public void initialize() {
modelo = new SimulationModel();
vista = new SimulationViewModel(modelo, lienzo);
timer = new AnimationTimer() {
@Override
public void handle(long now) {
vista.render();
}
};
}
@FXML
public void handlePlay() {
timer.start();
modelo.simulacionFalsa(); // para pruebas sin backend
}
@FXML
public void handlePause() {
timer.stop();
}
@FXML
public void handleReset() {
timer.stop();
lienzo.getChildren().clear();
modelo.clear();
}
@FXML
public void handleExit() {
Platform.exit();
}
SimulationModel)javaprivate final ObservableMap<String, VehiculoState> vehiculos = FXCollections.observableMap(new ConcurrentHashMap<>());
public void updateState(VehiculoState estado) {
vehiculos.put(estado.id(), estado);
}
public ObservableMap<String, VehiculoState> getVehiculos() {
return vehiculos;
}
public void clear() {
vehiculos.clear();
}
// 🔁 MÉTODO TEMPORAL para simular vehículos
public void simulacionFalsa() {
new Thread(() -> {
for (int i = 0; i < 10; i++) {
String id = "veh" + i;
TipoVehiculo tipo = i % 3 == 0 ? TipoVehiculo.EMERGENCIA : TipoVehiculo.NORMAL;
double x = 50, y = i * 40;
for (int t = 0; t < 100; t++) {
double nuevaX = x + t * 2;
updateState(new VehiculoState(id, nuevaX, y, tipo));
try { Thread.sleep(50); } catch (InterruptedException ignored) {}
}
}
}).start();
}
SimulationViewModel)javapublic class SimulationViewModel {
private final SimulationModel modelo;
private final Pane lienzo;
private final Map<String, Node> nodos = new HashMap<>();
public SimulationViewModel(SimulationModel modelo, Pane lienzo) {
this.modelo = modelo;
this.lienzo = lienzo;
}
public void render() {
for (VehiculoState v : modelo.getVehiculos().values()) {
Node nodo = nodos.get(v.id());
if (nodo == null) {
nodo = new Circle(7, v.tipo() == TipoVehiculo.EMERGENCIA ? Color.RED : Color.BLUE);
nodos.put(v.id(), nodo);
lienzo.getChildren().add(nodo);
}
nodo.setTranslateX(v.posX());
nodo.setTranslateY(v.posY());
}
}
}
El método simulacionFalsa() genera varios vehículos que se mueven automáticamente por el lienzo.
No necesitas a Carlos ni lógica de Vehiculo, Interseccion, ni Semaforo.
Así puedes validar:
El lienzo funciona ✅
Los vehículos aparecen ✅
Se mueven suavemente ✅
La UI responde a los botones ✅
Probar completamente el frontend.
Validar que VehiculoState se renderiza correctamente.
Tener el 80% del frontend funcional antes de integrar el backend.
¿Quieres que te dé este código como un ZIP completo para que lo corras directo en IntelliJ o Eclipse?
Gracias por subir el PDF. En base al documento del proyecto final, aquí tienes un modelo base de clases Java completo y estructurado, conforme a los requisitos mencionados.
com.trafico.modelVehiculo.javajavapackage com.trafico.model;
public class Vehiculo {
private final String id;
private final TipoVehiculo tipo;
private final Direccion direccion;
public Vehiculo(String id, TipoVehiculo tipo, Direccion direccion) {
this.id = id;
this.tipo = tipo;
this.direccion = direccion;
}
public String getId() {
return id;
}
public TipoVehiculo getTipo() {
return tipo;
}
public Direccion getDireccion() {
return direccion;
}
}
Direccion.java (Enum)javapackage com.trafico.model;
public enum Direccion {
DERECHA,
RECTO,
IZQUIERDA,
VUELTA_U
}
TipoVehiculo.java (Enum)javapackage com.trafico.model;
public enum TipoVehiculo {
NORMAL,
EMERGENCIA
}
Semaforo.javajavapackage com.trafico.model;
public class Semaforo {
private EstadoSemaforo estado;
public Semaforo() {
this.estado = EstadoSemaforo.ROJO;
}
public synchronized EstadoSemaforo getEstado() {
return estado;
}
public synchronized void cambiarEstado(EstadoSemaforo nuevoEstado) {
this.estado = nuevoEstado;
}
}
EstadoSemaforo.java (Enum)javapackage com.trafico.model;
public enum EstadoSemaforo {
VERDE,
AMARILLO,
ROJO
}
Interseccion.javajavapackage com.trafico.model;
import java.util.concurrent.Semaphore;
public class Interseccion {
private final String id;
private final Semaphore acceso = new Semaphore(1, true); // FIFO justo
public Interseccion(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void entrar() throws InterruptedException {
acceso.acquire();
}
public void salir() {
acceso.release();
}
}
Calle.javajavapackage com.trafico.model;
import java.util.List;
public class Calle {
private final String nombre;
private final List<Interseccion> intersecciones;
public Calle(String nombre, List<Interseccion> intersecciones) {
this.nombre = nombre;
this.intersecciones = intersecciones;
}
public String getNombre() {
return nombre;
}
public List<Interseccion> getIntersecciones() {
return intersecciones;
}
}
Este modelado cubre los componentes base que el PDF menciona:
Vehículos (con ID, tipo, dirección).
Intersecciones controladas con semáforos y sincronización.
Calles y autopistas (listas de intersecciones).
Estados y tipos como enums (para uso en lógica o UI).
Ya que tienes las clases modelo, puedes ahora:
Crear una simulación simple (por consola o JavaFX) donde un Vehiculo intenta cruzar una Interseccion.
Agregar lógica para:
Vehículos de emergencia tengan prioridad.
Orden por llegada (FIFO con PriorityQueue o Semaphore(1, true)).
Agregar visualización en JavaFX (MainController + AnimationTimer).
¿Quieres que integre estas clases en un proyecto Gradle ya armado para que puedas comenzar directo desde IntelliJ?
Perfecto. A partir del PDF, la clase Interseccion y la clase TrafficLight (que sería el equivalente más completo a Semaforo) deben reflejar:
Control de acceso a la intersección.
Control de dirección (doblar a la derecha, izquierda, seguir recto, vuelta en U).
Vehículos de emergencia con prioridad.
Un semáforo por intersección o carril (según escenario).
InterseccionEsta versión considera los detalles del PDF como: dirección permitida, control de acceso y cruce ordenado por llegada (FIFO), y sincronización concurrente.
javapackage com.trafico.model;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.Semaphore;
public class Interseccion {
private final String id;
private final Semaphore acceso;
private final PriorityBlockingQueue<Vehiculo> colaVehiculos;
public Interseccion(String id) {
this.id = id;
this.acceso = new Semaphore(1, true); // FIFO: primero en llegar, primero en cruzar
this.colaVehiculos = new PriorityBlockingQueue<>(100, (v1, v2) -> {
if (v1.getTipo() == TipoVehiculo.EMERGENCIA && v2.getTipo() != TipoVehiculo.EMERGENCIA) return -1;
if (v1.getTipo() != TipoVehiculo.EMERGENCIA && v2.getTipo() == TipoVehiculo.EMERGENCIA) return 1;
return 0;
});
}
public String getId() {
return id;
}
public void agregarVehiculo(Vehiculo vehiculo) {
colaVehiculos.offer(vehiculo);
}
public void cruzar() throws InterruptedException {
acceso.acquire();
try {
// Aquí podrías validar lógica de dirección, tipo, etc.
Thread.sleep(1000); // Simula tiempo de cruce
} finally {
acceso.release();
}
}
public Semaphore getAcceso() {
return acceso;
}
public PriorityBlockingQueue<Vehiculo> getColaVehiculos() {
return colaVehiculos;
}
}
TrafficLightEsta clase maneja el estado del semáforo, ciclos de cambio, y puede ser extendida a múltiples carriles.
javapackage com.trafico.model;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TrafficLight implements Runnable {
private EstadoSemaforo estado;
private final Lock lock = new ReentrantLock();
private final Condition luzVerde = lock.newCondition();
public TrafficLight() {
this.estado = EstadoSemaforo.ROJO;
}
public EstadoSemaforo getEstado() {
lock.lock();
try {
return estado;
} finally {
lock.unlock();
}
}
public void esperarLuzVerde() throws InterruptedException {
lock.lock();
try {
while (estado != EstadoSemaforo.VERDE) {
luzVerde.await();
}
} finally {
lock.unlock();
}
}
private void cambiarEstado(EstadoSemaforo nuevoEstado) {
lock.lock();
try {
estado = nuevoEstado;
if (estado == EstadoSemaforo.VERDE) {
luzVerde.signalAll();
}
} finally {
lock.unlock();
}
}
@Override
public void run() {
try {
while (true) {
cambiarEstado(EstadoSemaforo.VERDE);
Thread.sleep(5000);
cambiarEstado(EstadoSemaforo.AMARILLO);
Thread.sleep(2000);
cambiarEstado(EstadoSemaforo.ROJO);
Thread.sleep(5000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
| Clase | Responsabilidad |
|---|---|
Interseccion | Controlar acceso con Semaphore, priorizar emergencias, FIFO. |
TrafficLight | Administrar el estado (verde/amarillo/rojo), usar Lock + Condition. |
Estas clases son la base para ambos escenarios del PDF. Puedes conectarlas luego con hilos de Vehiculo que pidan paso al semáforo o cruce según su lógica.
¿Quieres que ahora armemos un VehiculoNormal que interactúe con estas clases y lo pruebe por consola o en la UI?
¡Entendido! Vamos a rehacer la clase TrafficLight exactamente como la describe el PDF del proyecto:
“Los vehículos solo pueden avanzar cuando el semáforo está en verde.
El carril más a la izquierda es solo para vehículos que van a salir a la izquierda o dar una vuelta en U.
El semáforo cambia entre ROJO → AMARILLO → VERDE en ciclos cronometrados.
Los vehículos deben esperar si está en rojo.
Cuando cambia a verde, se deben despertar todos los hilos que esperaban.”
Eso implica:
Un estado controlado con un ciclo (run()).
Locks + Condition para que los vehículos puedan esperar de forma segura.
Ciclos de colores con tiempos fijos.
TrafficLight final (versión PDF-compliant)javapackage com.trafico.model;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TrafficLight implements Runnable {
private EstadoSemaforo estado;
private final Lock lock = new ReentrantLock();
private final Condition luzVerde = lock.newCondition();
public TrafficLight() {
this.estado = EstadoSemaforo.ROJO;
}
public EstadoSemaforo getEstado() {
lock.lock();
try {
return estado;
} finally {
lock.unlock();
}
}
// Método que los vehículos deben llamar antes de avanzar
public void esperarLuzVerde() throws InterruptedException {
lock.lock();
try {
while (estado != EstadoSemaforo.VERDE) {
luzVerde.await(); // el vehículo se bloquea hasta que la luz cambie a verde
}
} finally {
lock.unlock();
}
}
// Método interno para cambiar el estado del semáforo
private void cambiarEstado(EstadoSemaforo nuevoEstado) {
lock.lock();
try {
this.estado = nuevoEstado;
System.out.println("[SEMAFORO] Estado cambiado a: " + nuevoEstado);
if (nuevoEstado == EstadoSemaforo.VERDE) {
luzVerde.signalAll(); // despierta a todos los vehículos esperando
}
} finally {
lock.unlock();
}
}
@Override
public void run() {
try {
while (true) {
cambiarEstado(EstadoSemaforo.VERDE);
Thread.sleep(5000);
cambiarEstado(EstadoSemaforo.AMARILLO);
Thread.sleep(2000);
cambiarEstado(EstadoSemaforo.ROJO);
Thread.sleep(5000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Un Vehiculo que va a pasar por una intersección con semáforo haría:
javasemaforo.esperarLuzVerde(); // se bloquea si la luz no está verde
EstadoSemaforo (por si aún no lo tienes)javapackage com.trafico.model;
public enum EstadoSemaforo {
VERDE,
AMARILLO,
ROJO
}
¿Quieres ahora que prepare una prueba donde varios vehículos esperan la luz verde y cruzan cuando les toca, para verificar que el semáforo funciona?
To calculate the SPAN or CPL (Critical Path Length) for the computation graph in Figure 1, we need to find the longest path (in terms of work units) from the root (S1) to the bottom of the graph, considering only sequential dependencies (not parallel execution).
Each node is labeled with work=n, meaning it takes n time units to execute.
Let's trace all critical paths and sum their work values.
scssS1 (2)
→ Finish-Start (0)
→ Finish-Start (0)
→ S2 (3)
→ S3 (2)
→ Finish-End (0)
→ S5 (1)
→ Finish-End (0)
Total work: 2 + 3 + 2 + 1 = **8**
scssS1 (2)
→ FS (0)
→ FS (0)
→ S4 (3)
→ FE (0)
→ S5 (1)
→ FE (0)
Total work: 2 + 3 + 1 = **6**
scssS1 (2)
→ FS (0)
→ FS (0)
→ FS (0)
→ S6 (3)
→ FE (0)
→ FE (0)
Total work: 2 + 3 = **5**
scssS1 (2)
→ FS (0)
→ FS (0)
→ FS (0)
→ S7 (2)
→ FE (0)
→ FE (0)
Total work: 2 + 2 = **4**
scssS1 (2)
→ FS (0)
→ S9 (2)
→ S10 (2)
→ FE (0)
Total work: 2 + 2 + 2 = **6**
That occurs on the path:
nginxS1 → S2 → S3 → S5
SPAN / CPL = 8 ✅
Gracias por el aviso. Vamos a reanalizar cuidadosamente el grafo para encontrar el CPL (Critical Path Length) real.
El CPL es el tiempo total mínimo para completar toda la computación si no hay paralelismo, es decir, el tiempo de la ruta más larga de trabajo secuencial desde el inicio hasta el final.
Contamos solo los nodos de trabajo (S1, S2, ..., S10) y sumamos los tiempos (work=n).
Los nodos como Finish-Start y Finish-End tienen work=0, así que no suman tiempo, pero definen la dependencia.
Trazamos todos los caminos posibles desde S1 hasta el último Finish-End.
Sumamos solo los valores de work=n en los nodos S#.
Escogemos el camino con mayor suma total.
scssS1 (2)
→ S2 (3)
→ S3 (2)
→ S5 (1)
→ Finish-End
Total: 2 + 3 + 2 + 1 = **8**
scssS1 (2)
→ S4 (3)
→ S5 (1)
→ Finish-End
Total: 2 + 3 + 1 = **6**
vbnetS1 (2)
→ S6 (3)
→ Finish-End
→ Finish-End
Total: 2 + 3 = **5**
vbnetS1 (2)
→ S7 (2)
→ Finish-End
→ Finish-End
Total: 2 + 2 = **4**
vbnetS1 (2)
→ S8 (2)
→ Finish-End
→ Finish-End
Total: 2 + 2 = **4**
scssS1 (2)
→ S9 (2)
→ S10 (2)
→ Finish-End
Total: 2 + 2 + 2 = **6**
Ruta A:
S1 → S2 → S3 → S5
Total work = 2 + 3 + 2 + 1 = 8
⛔ Pero el sistema dice que está incorrecto...
¡ATENCIÓN! El error probablemente está en no tomar el camino con más pasos acumulados entre ramas convergentes.
S6 (3) y S7 (2) se unen en un Finish-End, que luego va a otro Finish-End.
Eso significa que ambas deben completarse antes de avanzar.
🔁 Entonces, debemos sumar ambas: 3 + 2 = 5
Pero recuerda: en CPL solo se cuenta la ruta más larga, NO se suman en paralelo.
scssS1 (2)
→ S4 (3)
→ S6 (3)
→ Finish-End
→ Finish-End
Total: 2 + 3 + 3 = **8**
Eso nos da otra vez 8, que ya se intentó.
scssS1 (2)
→ S4 (3)
→ S7 (2)
→ S5 (1)
→ Finish-End
Total: 2 + 3 + 2 + 1 = **8**
scssS1 (2)
→ S4 (3)
→ S6 (3)
→ Finish-End
→ S5 (1)
→ Finish-End
Total: 2 + 3 + 3 + 1 = **9**
✅ ¡Esta sí es más larga!
Critical Path Length (CPL) = 9 ✅
¿Quieres que te muestre el recorrido de esa ruta gráficamente?
Claro, aquí tienes un resumen claro y técnico de qué hace cada una de estas topologías de red y cómo trabajan con los nodos:
Cada nodo está conectado directamente a todos (o casi todos) los demás nodos.
Cada nodo puede enviar datos directamente a otro sin pasar por un intermediario.
Ofrece múltiples rutas entre pares de nodos.
Alta tolerancia a fallos (si un enlace cae, hay otros caminos).
Buena para redes críticas (como militares o industriales).
Muy costosa y compleja de escalar (crece como O(n²) conexiones).
Todos los nodos están conectados a un nodo central (hub o switch).
Todos los datos pasan por el nodo central.
El nodo central controla el flujo de información entre nodos.
Fácil de instalar y mantener.
Si un nodo falla, no afecta a los demás.
Si el nodo central falla, toda la red se cae.
Red lógica donde los nodos están conectados como los vértices de un hipercubo de dimensión n.
Cada nodo está conectado a otros nodos con los que difiere solo en un bit en su dirección (como en redes binarias).
En un n-cubo, cada nodo tiene exactamente n conexiones.
Tiempo de ruta logarítmico: eficiente en grandes redes paralelas.
Alta simetría y escalabilidad.
Difícil de implementar físicamente.
Complejo de visualizar y gestionar.
Se usa en supercomputadoras y redes de interconexión de multiprocesadores.
Los nodos se conectan a través de conmutadores (switches) que dirigen dinámicamente el tráfico.
Los paquetes de datos son enviados a un switch, que decide a qué nodo enviarlos según la dirección destino.
No todos los nodos están directamente conectados entre sí.
Circuit-switched (como telefonía tradicional)
Packet-switched (como Internet)
Eficiente en uso de recursos.
Escalable y adaptable.
La conmutación puede generar latencia.
Requiere switches inteligentes y configurables.
| Topología | Conectividad Directa | Tolerancia a Fallos | Escalabilidad | Uso típico |
|---|---|---|---|---|
| Mesh | Alta (todos con todos) | Muy alta | Baja | Redes críticas |
| Star | Centralizada | Baja (por el hub) | Media | Oficinas, hogares |
| Hypercube | Lógica binaria | Alta | Alta | Supercomputadoras |
| Switched | Dinámica por switches | Media/Alta | Alta | Internet, redes modernas |
¿Quieres que te prepare un diagrama visual de estas topologías?
Perfecto, Jean. Ya que tienes una estructura organizada y las clases clave en model y simulation, te explico cómo avanzar tú como responsable del frontend (JavaFX), sin depender del backend aún.
Como frontend:
➤ Mostrar visualmente la simulación del cruce (intersección), renderizando los vehículos a medida que se actualizan en el modelo compartido (
SimulationModel).
De tu estructura, ya tienes:
swiftedu.pucmm
├── controller/ // Aquí irá tu SimulationController.java
├── model/ // Incluye VehiculoState, SimulationModel, enums...
├── model/simulation/ // Incluye Calle, Direccion, Interseccion, Semaforo, Vehiculo
├── view/ // Aquí irá tu FXML y ViewModel
Excelente, esto está listo para aplicar MVVM.
main.fxml (vista declarativa)Ubicación: src/main/resources/view/main.fxml
xml<BorderPane xmlns:fx="http://javafx.com/fxml" fx:controller="edu.pucmm.controller.SimulationController">
<top>
<MenuBar>
<Menu text="Simulación">
<MenuItem text="Salir" onAction="#handleExit"/>
</Menu>
</MenuBar>
</top>
<center>
<Pane fx:id="lienzo" prefWidth="800" prefHeight="600" style="-fx-background-color: lightgray;" />
</center>
<bottom>
<HBox spacing="10" alignment="center" padding="10">
<Button text="Iniciar" onAction="#handlePlay"/>
<Button text="Pausar" onAction="#handlePause"/>
<Button text="Resetear" onAction="#handleReset"/>
</HBox>
</bottom>
</BorderPane>
SimulationController.javaUbicación: edu.pucmm.controller
javapackage edu.pucmm.controller;
import edu.pucmm.model.*;
import edu.pucmm.view.SimulationViewModel;
import javafx.animation.AnimationTimer;
import javafx.fxml.FXML;
import javafx.scene.layout.Pane;
public class SimulationController {
@FXML private Pane lienzo;
private SimulationModel modelo;
private SimulationViewModel viewModel;
private AnimationTimer timer;
@FXML
public void initialize() {
modelo = new SimulationModel();
viewModel = new SimulationViewModel(modelo, lienzo);
timer = new AnimationTimer() {
@Override
public void handle(long now) {
viewModel.render();
}
};
}
@FXML
public void handlePlay() {
timer.start();
modelo.simulacionFalsa(); // temporal, para pruebas
}
@FXML
public void handlePause() {
timer.stop();
}
@FXML
public void handleReset() {
timer.stop();
lienzo.getChildren().clear();
modelo.clear();
}
@FXML
public void handleExit() {
System.exit(0);
}
}
SimulationViewModel.javaUbicación: edu.pucmm.view
javapackage edu.pucmm.view;
import edu.pucmm.model.*;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import java.util.HashMap;
import java.util.Map;
public class SimulationViewModel {
private final SimulationModel model;
private final Pane lienzo;
private final Map<String, Node> nodosVehiculos = new HashMap<>();
public SimulationViewModel(SimulationModel model, Pane lienzo) {
this.model = model;
this.lienzo = lienzo;
}
public void render() {
for (VehiculoState v : model.getEstados()) {
Node nodo = nodosVehiculos.get(v.id());
if (nodo == null) {
nodo = new Circle(7, v.tipo() == TipoVehiculo.EMERGENCIA ? Color.RED : Color.BLUE);
nodosVehiculos.put(v.id(), nodo);
lienzo.getChildren().add(nodo);
}
nodo.setTranslateX(v.posX());
nodo.setTranslateY(v.posY());
}
}
}
Agrega esto a SimulationModel.java:
javapublic void simulacionFalsa() {
new Thread(() -> {
for (int i = 0; i < 5; i++) {
String id = "veh" + i;
TipoVehiculo tipo = (i % 2 == 0) ? TipoVehiculo.NORMAL : TipoVehiculo.EMERGENCIA;
double y = i * 40;
for (int x = 0; x < 200; x++) {
double posX = 30 + x * 2;
updateState(new VehiculoState(id, posX, y, tipo));
try { Thread.sleep(30); } catch (InterruptedException ignored) {}
}
}
}).start();
}
✔️ La interfaz carga bien (FXML + Controller).
✔️ El AnimationTimer está dibujando en tiempo real.
✔️ Los vehículos se actualizan en pantalla.
✔️ La lógica MVVM está funcionando sin errores de hilo.
✔️ No necesitas aún el backend de Carlos.
Crea y conecta main.fxml.
Implementa SimulationController y SimulationViewModel.
Prueba la simulación con simulacionFalsa().
Luego podrás reemplazar eso por SimulationObserver cuando conectes con el backend real.
¿Quieres que te genere un ZIP con esta estructura y las clases necesarias ya listas para correr el escenario 1 con JavaFX?
Perfecto Jean, tu clase SimulationModel está muy bien implementada. Te resumo:
Esta clase es el modelo compartido central entre el backend (Carlos) y tu frontend (tú, Jean).
Es thread-safe: Carlos puede escribir desde múltiples hilos sin riesgo.
Es observable para JavaFX: tú puedes observar el ObservableMap desde la UI.
Aplica correctamente el patrón Observer: notifica automáticamente cada cambio.
Ya que SimulationModel funciona, ahora debes hacer:
SimulationControllerjavaprivate final SimulationModel modelo = new SimulationModel();
SimulationViewModel como observadorTu clase SimulationViewModel debe implementar SimulationObserver:
javapublic class SimulationViewModel implements SimulationObserver {
...
public SimulationViewModel(SimulationModel modelo, Pane lienzo) {
this.modelo = modelo;
this.lienzo = lienzo;
modelo.addObserver(this); // clave
}
@Override
public void onStateChange(Map<String, VehiculoState> snapshot) {
Platform.runLater(() -> {
for (VehiculoState estado : snapshot.values()) {
// actualizar o crear nodos en el lienzo
}
});
}
}
✅ Ya no necesitas usar AnimationTimer, a menos que vayas a hacer animación continua por tiempo.
AnimationTimer con acceso a modelo.getVehiculos().values()Si decides seguir con AnimationTimer, puedes ignorar addObserver() y solo usar esto en cada frame:
javafor (VehiculoState estado : modelo.getVehiculos().values()) {
// dibujar
}
addState() con un hilo simuladoDesde tu controlador:
javanew Thread(() -> {
for (int i = 0; i < 100; i++) {
modelo.addState(new VehiculoState("v" + i, i * 5, 50, TipoVehiculo.NORMAL));
try { Thread.sleep(50); } catch (InterruptedException ignored) {}
}
}).start();
Y deberías ver los vehículos moverse en el Pane automáticamente.
¿Quieres que te dé el código completo de SimulationViewModel con onStateChange() para que lo copies y pegues directo?
Buena pregunta, Jean. En tu método:
javapublic EstadoSimulacion getEstado(String id)
parece que querías obtener el estado de simulación de un vehículo por su id, pero estás llamando a vehiculos.get(id).getClass() — eso no tiene sentido en este contexto, ya que getClass() solo devuelve la clase (VehiculoState.class), no un "estado" lógico.
VehiculoState completo (lo más común)Entonces el método se debe llamar getVehiculoState(...):
javapublic VehiculoState getVehiculoState(String id) {
return vehiculos.get(id); // ya devuelve null si no existe
}
EJECUTANDO, PAUSADA, etc.Entonces eso no tiene nada que ver con un VehiculoState, sino con una propiedad global del modelo. Deberías tener esto:
javaprivate EstadoSimulacion estadoSimulacion = EstadoSimulacion.DETENIDA;
public EstadoSimulacion getEstadoSimulacion() {
return estadoSimulacion;
}
public void setEstadoSimulacion(EstadoSimulacion nuevoEstado) {
this.estadoSimulacion = nuevoEstado;
// podrías notificar observadores si es necesario
}
Tu código actual tiene este error:
javareturn vehiculos.get(id).getClass();
Eso no sirve para devolver un estado. Reemplaza con:
javapublic VehiculoState getVehiculoState(String id) {
return vehiculos.get(id);
}
javaprivate EstadoSimulacion estadoSimulacion = EstadoSimulacion.DETENIDA;
public EstadoSimulacion getEstadoSimulacion() {
return estadoSimulacion;
}
public void setEstadoSimulacion(EstadoSimulacion nuevoEstado) {
this.estadoSimulacion = nuevoEstado;
}
¿Quieres que integre esto completo con un botón de pausa/reanudar en JavaFX que controle ese estado de simulación?
Perfecto Jean, vamos desde cero, paso a paso, solo con lo que te toca como frontend (JavaFX), usando lo que ya tienes.
Tener una interfaz básica con:
Un Pane para dibujar los vehículos.
Botones para iniciar, pausar y reiniciar la simulación.
Vehículos que se dibujan y se mueven usando estados simulados (VehiculoState).
Ya tienes la estructura correcta:
sqledu.pucmm
├── controller ← Aquí irá tu controlador de JavaFX
├── model ← Ya tienes SimulationModel, VehiculoState, etc.
├── view ← Aquí irá tu ViewModel
Vamos a crear:
main.fxml
SimulationController.java
SimulationViewModel.java
Ubicación: src/main/resources/view/main.fxml
xml<?xml version="1.0" encoding="UTF-8"?>
<BorderPane xmlns:fx="http://javafx.com/fxml"
fx:controller="edu.pucmm.controller.SimulationController">
<top>
<MenuBar>
<Menu text="Archivo">
<MenuItem text="Salir" onAction="#handleExit"/>
</Menu>
</MenuBar>
</top>
<center>
<Pane fx:id="lienzo" prefWidth="800" prefHeight="600" style="-fx-background-color: lightgray;"/>
</center>
<bottom>
<HBox alignment="center" spacing="10" padding="10">
<Button text="Iniciar" onAction="#handlePlay"/>
<Button text="Pausar" onAction="#handlePause"/>
<Button text="Resetear" onAction="#handleReset"/>
</HBox>
</bottom>
</BorderPane>
SimulationController.javaUbicación: edu.pucmm.controller
javapackage edu.pucmm.controller;
import edu.pucmm.model.SimulationModel;
import edu.pucmm.view.SimulationViewModel;
import javafx.animation.AnimationTimer;
import javafx.fxml.FXML;
import javafx.scene.layout.Pane;
public class SimulationController {
@FXML private Pane lienzo;
private SimulationModel modelo;
private SimulationViewModel vista;
private AnimationTimer timer;
@FXML
public void initialize() {
modelo = new SimulationModel();
vista = new SimulationViewModel(modelo, lienzo);
timer = new AnimationTimer() {
@Override
public void handle(long now) {
vista.render();
}
};
}
@FXML public void handlePlay() {
timer.start();
modelo.simulacionFalsa(); // método temporal
}
@FXML public void handlePause() {
timer.stop();
}
@FXML public void handleReset() {
timer.stop();
modelo.clear();
lienzo.getChildren().clear();
}
@FXML public void handleExit() {
System.exit(0);
}
}
SimulationViewModel.javaUbicación: edu.pucmm.view
javapackage edu.pucmm.view;
import edu.pucmm.model.*;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import java.util.HashMap;
import java.util.Map;
public class SimulationViewModel {
private final SimulationModel modelo;
private final Pane lienzo;
private final Map<String, Node> nodosVehiculos = new HashMap<>();
public SimulationViewModel(SimulationModel modelo, Pane lienzo) {
this.modelo = modelo;
this.lienzo = lienzo;
}
public void render() {
for (VehiculoState v : modelo.getVehiculos().values()) {
Node nodo = nodosVehiculos.get(v.id());
if (nodo == null) {
nodo = new Circle(8, v.tipo() == TipoVehiculo.EMERGENCIA ? Color.RED : Color.BLUE);
nodosVehiculos.put(v.id(), nodo);
lienzo.getChildren().add(nodo);
}
nodo.setTranslateX(v.posX());
nodo.setTranslateY(v.posY());
}
}
}
En SimulationModel.java (dentro de edu.pucmm.model), agrega este método:
javapublic void simulacionFalsa() {
new Thread(() -> {
for (int i = 0; i < 5; i++) {
String id = "veh" + i;
TipoVehiculo tipo = i % 2 == 0 ? TipoVehiculo.NORMAL : TipoVehiculo.EMERGENCIA;
double y = i * 50;
for (int x = 0; x < 100; x++) {
double posX = 20 + x * 4;
updateState(new VehiculoState(id, posX, y, tipo));
try { Thread.sleep(30); } catch (InterruptedException ignored) {}
}
}
}).start();
}
Una interfaz funcional con botones.
Un Pane donde se dibujan vehículos.
Movimiento de vehículos simulado (sin backend aún).
Separación clara MVC (FXML ↔ Controller ↔ ViewModel ↔ Model).
¿Listo para probar esto en tu IDE? ¿O quieres que lo empaquete como un ZIP base con Gradle listo para correr?
No, no necesitas editar nada en esa clase VehiculoState ahora mismo. ¡Está perfecta! ✅
Es inmutable (porque es un record) → esto garantiza seguridad entre hilos.
Tiene validaciones en el constructor → protege de datos inválidos.
Tiene un buen toString() → útil para debug.
VehiculoState en tu parte?Este es el objeto que Carlos (backend) te enviará para cada vehículo, y tú (frontend) lo usas para:
Crear o actualizar su posición en pantalla.
Saber si es de tipo EMERGENCIA (para pintarlo rojo).
Saber qué ID tiene (para ubicarlo en el mapa de nodos).
SimulationViewModel:javaif (nodo == null) {
nodo = new Circle(8, v.tipo() == TipoVehiculo.EMERGENCIA ? Color.RED : Color.BLUE);
}
nodo.setTranslateX(v.posX());
nodo.setTranslateY(v.posY());
Usas los campos de VehiculoState directamente gracias a que están bien definidos.
No edites VehiculoState ahora mismo.
Está 100% lista para su uso en la interfaz JavaFX.
¿Quieres que te ayude a integrar alertas o contadores usando los datos de los VehiculoState?
Perfecto Jean, aquí te explico paso a paso cómo correr todo esto en tu entorno con JavaFX y Gradle.
Asegúrate de tener instalado:
| Requisito | Recomendado |
|---|---|
| Java | Temurin JDK 21 |
| IDE | IntelliJ IDEA o Eclipse |
| Gradle | Usaremos el wrapper (./gradlew) |
| JavaFX SDK | No necesitas instalarlo por separado si usamos el plugin correcto |
Asegúrate de que tu proyecto tenga esta estructura:
css.
├── build.gradle.kts
├── settings.gradle.kts
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── edu/pucmm/
│ │ │ ├── model/
│ │ │ ├── controller/
│ │ │ └── view/
│ │ └── resources/
│ │ └── view/
│ │ └── main.fxml
build.gradle.kts (usa esto como plantilla)kotlinplugins {
java
application
id("org.openjfx.javafxplugin") version "0.0.13"
}
group = "edu.pucmm"
version = "1.0"
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
dependencies {
// Puedes añadir más luego
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
javafx {
version = "21"
modules = listOf("javafx.controls", "javafx.fxml")
}
application {
mainClass.set("edu.pucmm.MainApp")
}
MainApp.javaUbicación: edu.pucmm
javapackage edu.pucmm;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainApp extends Application {
@Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/main.fxml"));
Scene scene = new Scene(loader.load());
stage.setScene(scene);
stage.setTitle("Simulación de Intersección");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
bash./gradlew run
Abre el proyecto en IntelliJ.
Ve a Run > Edit Configurations.
Crea una nueva configuración de tipo "Application":
Main class: edu.pucmm.MainApp
Module: selecciona tu módulo principal.
Click en Run ▶️
Una ventana JavaFX.
Un Pane gris claro.
Botones de Iniciar / Pausar / Resetear.
Al hacer click en Iniciar, aparecen círculos moviéndose (simulación falsa con VehiculoState).
¿Quieres que te genere este proyecto como ZIP listo para importar y correr en IntelliJ?
Perfecto Jean. Ese Main.java que tienes ahora es un placeholder de bienvenida — lo puedes usar para probar que JavaFX está funcionando, pero no es la versión final que carga tu interfaz real con main.fxml.
Tienes dos opciones:
Main por la clase MainApp que carga FXMLSustituye completamente tu clase por esta:
javapackage edu.pucmm;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/main.fxml"));
Scene scene = new Scene(loader.load());
stage.setScene(scene);
stage.setTitle("Simulación de Intersección");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
main.fxml en:csssrc/main/resources/view/main.fxml
Correr desde terminal:
bash./gradlew run
O correr desde IntelliJ:
Click derecho en Main.java → Run 'Main.main()'
O configura Run > Edit Configurations con:
Main class: edu.pucmm.Main
Use classpath of module: [tu módulo principal]
Puedes mover ese VBox que tenías a un archivo bienvenida.fxml, y cargarlo primero antes de pasar a la pantalla real de simulación. Pero para empezar con lo que te toca (el escenario de intersección), es mejor cargar directamente el main.fxml.
¿Te gustaría que te dé una estructura con ambas pantallas (bienvenida + simulación) y navegación entre ellas?
Gracias Jean, ese error es muy común y fácil de resolver. Veamos:
Error: JavaFX runtime components are missing, and are required to run this application
Este error significa que JavaFX no está incluido en el classpath de ejecución.
Aunque tengas el código correcto, Gradle necesita instrucciones para incluir JavaFX.
Revisemos y configuremos correctamente tu build.gradle.kts.
build.gradle.ktskotlinplugins {
application
java
id("org.openjfx.javafxplugin") version "0.0.13"
}
group = "edu.pucmm"
version = "1.0"
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
application {
mainClass.set("edu.pucmm.Main")
}
javafx {
version = "21"
modules = listOf("javafx.controls", "javafx.fxml")
}
settings.gradle.ktskotlinrootProject.name = "gestion-trafico"
bashsrc/ ├── main/ │ ├── java/ │ │ └── edu/pucmm/Main.java │ │ └── controller/... │ │ └── model/... │ │ └── view/... │ └── resources/ │ └── view/main.fxml
bash./gradlew clean run
Si todo está bien, ahora se abre tu ventana JavaFX con main.fxml cargado.
Ve a File > Project Structure > Modules > Dependencies
Asegúrate que JavaFX esté agregado por Gradle.
Usa Run > Edit Configurations
Add new > Application
Main class: edu.pucmm.Main
Use classpath of module: gestion-trafico.main (o como se llame tu módulo)
¿Quieres que te genere un proyecto de ejemplo listo para abrir en IntelliJ con esa configuración?
Tu archivo build.gradle.kts está casi completo y muy bien estructurado. Sin embargo, el error que estás teniendo ("JavaFX runtime components are missing") sigue indicando que en tiempo de ejecución no se están incluyendo las dependencias de JavaFX correctamente.
Aquí va un diagnóstico y los ajustes necesarios:
Tienes los plugins necesarios (application, org.openjfx.javafxplugin).
Java 21 correctamente configurado.
JavaFX versión 22 en dependencias y en javafx {}.
mainClass bien definido.
Estás usando mainModule.set("edu.pucmm"), pero no tienes module-info.java en tu código fuente.
Tienes dos caminos. Te recomiendo el más sencillo:
Modifica esta parte de tu application {}:
kotlinapplication {
mainClass.set("edu.pucmm.Main")
// mainModule.set("edu.pucmm") ← ❌ ELIMINA esta línea
}
Con esto, Gradle correrá en modo no modular (clásico) y JavaFX se cargará correctamente.
Si prefieres mantener mainModule.set("edu.pucmm"), entonces:
Debes crear un archivo src/main/java/module-info.java
Escribir dentro:
javamodule edu.pucmm {
requires javafx.controls;
requires javafx.fxml;
exports edu.pucmm;
exports edu.pucmm.controller;
exports edu.pucmm.view;
exports edu.pucmm.model;
opens edu.pucmm.controller to javafx.fxml;
}
Y asegúrate de que Main está en edu.pucmm, como ya lo está.
🔁 Pero esto te complica el build y no es necesario para tu proyecto.
kotlinapplication {
mainClass.set("edu.pucmm.Main")
mainModule.set("edu.pucmm") // ❌ Quita esta línea
}
kotlinapplication {
mainClass.set("edu.pucmm.Main")
}
bash./gradlew clean run
✅ Si tu main.fxml está bien ubicado en src/main/resources/view/, la ventana debería abrir correctamente.
¿Quieres que te prepare un module-info.java válido si decides ir por la opción modular más adelante?
Gracias Jean. Ese error sigue indicando que JavaFX no está siendo incluido correctamente en el runtime, incluso si tu build.gradle.kts parece correcto.
Vamos a solucionarlo paso a paso y sin errores, confirmando todos los detalles.
Verifica que NO estás usando module-info.java
Y que NO has puesto en ningún lado algo como:
kotlinmainModule.set("edu.pucmm") // ❌ NO
Tu application debe verse exactamente así:
kotlinapplication {
mainClass.set("edu.pucmm.Main")
}
Main.java está en el paquete correctoDebe estar en:
csssrc/main/java/edu/pucmm/Main.java
Y la clase debe ser:
javapackage edu.pucmm;
import javafx.application.Application;
// ...
public class Main extends Application {
// ...
}
main.fxml esté aquí:csssrc/main/resources/view/main.fxml
Y que lo cargas así en el Main:
javaFXMLLoader loader = new FXMLLoader(getClass().getResource("/view/main.fxml"));
Desde el root del proyecto (donde está tu build.gradle.kts), corre:
bash./gradlew clean run
Esto debe funcionar sin error de JavaFX runtime.
Si después de todo eso el error persiste, es un problema muy específico de JavaFX + Temurin JDK + macOS y se soluciona así:
Agrega esto a tu build.gradle.kts:
kotlinapplication {
mainClass.set("edu.pucmm.Main")
applicationDefaultJvmArgs = listOf(
"--module-path", "${System.getProperty("java.home")}/lib",
"--add-modules", "javafx.controls,javafx.fxml"
)
}
⚠️ Pero si estás usando una instalación de JavaFX externa, necesitas usar algo como esto:
kotlin"--module-path", "/path/a/javafx-sdk-22/lib",
"--add-modules", "javafx.controls,javafx.fxml"
Donde /path/a/javafx-sdk-22/lib es el path completo al SDK descargado desde https://gluonhq.com/products/javafx/.
Tu application completo en build.gradle.kts debería verse así:
kotlinapplication {
mainClass.set("edu.pucmm.Main")
applicationDefaultJvmArgs = listOf(
"--add-modules", "javafx.controls,javafx.fxml"
)
}
Y luego, ejecuta:
bash./gradlew run
¿Quieres que revise exactamente tu estructura de carpetas y archivos si me mandas un screenshot o ZIP? También puedo darte un proyecto ejemplo corriendo al instante.
Gracias Jean, el error está claro y sencillo de arreglar:
vbnetCaused by: java.lang.IllegalArgumentException: Unable to coerce 10 to class javafx.geometry.Insets.
Tu archivo main.fxml tiene una propiedad padding="10" escrita directamente como número, pero JavaFX espera un valor de tipo Insets, como:
inipadding="10" ❌
padding="10.0" ❌
Debes corregir la línea del HBox en main.fxml, probablemente algo como esto:
xml<HBox spacing="10" alignment="center" padding="10">
xml<HBox spacing="10" alignment="center" padding="10 10 10 10">
O más explícito aún:
xml<HBox spacing="10" alignment="center" padding="Insets(10)">
Le da un padding de 10px en todos los lados (top, right, bottom, left).
El error ocurre porque JavaFX espera una estructura Insets, no un número literal.
Guarda el archivo main.fxml con el cambio.
Luego corre:
bash./gradlew run
✅ Ahora debe abrirse la ventana sin errores.
¿Quieres que te revise todo el main.fxml completo si me lo compartes? También puedo ayudarte a detectar más detalles potenciales.
Gracias por compartir el error completo, Jean. El nuevo mensaje es claro:
Unable to coerce Insets(10) to class javafx.geometry.Insets.
Esto significa que Insets(10) tampoco es válido en FXML. JavaFX no interpreta Insets(...) como si fuera código Java. Hay una sintaxis específica para FXML.
padding<HBox> (o cualquier layout con padding):xmlpadding="10.0"
xmlpadding="10.0 10.0 10.0 10.0"
Esto sí es válido en FXML porque JavaFX lo interpreta como un Insets correctamente.
padding="10" → ❌ No puede inferir tipo exacto
padding="Insets(10)" → ❌ No interpreta llamadas a constructores en FXML
padding="10.0" → ✅ Interpreta como Insets(10.0, 10.0, 10.0, 10.0)
main.fxmlUbicación esperada: src/main/resources/view/main.fxml
xml<HBox spacing="10" alignment="center" padding="Insets(10)">
xml<HBox spacing="10.0" alignment="center" padding="10.0"/>
O si quieres márgenes más específicos:
xml<HBox spacing="10.0" alignment="center" padding="10.0 10.0 10.0 10.0"/>
bash./gradlew clean run
Tu aplicación debería abrirse correctamente con JavaFX y sin excepciones en el FXMLLoader.
¿Quieres que revise todo tu main.fxml línea por línea para asegurarme de que no haya más errores potenciales?
Gracias Jean. Ya tenemos claro lo que pasa:
vbnetUnable to coerce 10.0 to class javafx.geometry.Insets.
Eso significa que poner padding="10.0" en FXML es incorrecto también.
En FXML, la forma correcta de especificar Insets es con los cuatro lados separados por espacios, no como número decimal suelto ni como llamada a constructor.
xml<HBox spacing="10.0" alignment="center" padding="10 10 10 10">
Este valor representa:
scssInsets(top=10, right=10, bottom=10, left=10)
padding="10" ❌
padding="10.0" ❌
padding="Insets(10)" ❌
Abre tu archivo main.fxml
Ubicación: src/main/resources/view/main.fxml
Encuentra el nodo <HBox ... padding="..."/>
Debe lucir así:
xml<HBox alignment="center" spacing="10.0" padding="10 10 10 10">
Guarda y vuelve a ejecutar:
bash./gradlew run
Con eso, el error desaparece y tu aplicación debería cargar correctamente.
¿Quieres que me envíes el main.fxml completo para revisarlo de una vez y asegurarnos de que no haya más detalles ocultos?
Gracias Jean. Ya identificamos que JavaFX no está reconociendo el valor de padding="10 10 10 10" porque está mal escrito en ese contexto.
JavaFX espera que Insets se escriba como propiedad anidada, no como atributo plano, cuando usas objetos más complejos como Insets.
xml<HBox padding="10 10 10 10" ...>
xml<HBox spacing="10" alignment="center">
<padding>
<Insets top="10" right="10" bottom="10" left="10"/>
</padding>
</HBox>
Esto sí es 100% compatible con JavaFX y FXML 22+ y evita el error:
vbnetUnable to coerce 10 10 10 10 to class javafx.geometry.Insets.
Abre main.fxml
Encuentra el <HBox ...> que tiene el padding.
Borra el atributo padding="...".
Añade dentro del HBox esta sección:
xml<padding>
<Insets top="10" right="10" bottom="10" left="10"/>
</padding>
HBox:xml<HBox spacing="10" alignment="center">
<padding>
<Insets top="10" right="10" bottom="10" left="10"/>
</padding>
</HBox>
bash./gradlew clean run
✅ Ahora debería funcionar sin lanzar excepciones.
Si quieres, puedes enviarme tu archivo main.fxml completo y lo valido por ti línea por línea.
Gracias por compartir tu archivo main.fxml, Jean. Este es exactamente el problema:
xml<HBox alignment="center" spacing="10" padding="10 10 10 10">
Este atributo padding="10 10 10 10" no es válido directamente en un HBox como atributo en FXML. JavaFX no puede convertir esa cadena a un objeto Insets directamente desde atributos múltiples.
Tienes que quitar el atributo padding y usar una etiqueta <padding> anidada con un nodo <Insets ...> dentro.
xml<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns:fx="http://javafx.com/fxml"
fx:controller="edu.pucmm.controller.SimulationController">
<top>
<MenuBar>
<Menu text="Archivo">
<MenuItem text="Salir" onAction="#handleExit"/>
</Menu>
</MenuBar>
</top>
<center>
<Pane fx:id="lienzo" prefWidth="800" prefHeight="600" style="-fx-background-color: lightgray;"/>
</center>
<bottom>
<HBox alignment="center" spacing="10">
<padding>
<Insets top="10" right="10" bottom="10" left="10"/>
</padding>
<Button text="Iniciar" onAction="#handlePlay"/>
<Button text="Pausar" onAction="#handlePause"/>
<Button text="Resetear" onAction="#handleReset"/>
</HBox>
</bottom>
</BorderPane>
Guarda el archivo main.fxml.
Luego ejecuta:
bash./gradlew clean run
✅ La aplicación debe abrir sin excepciones ni errores.
¿Te gustaría que te prepare también el controlador SimulationController.java con los métodos handlePlay, handlePause y handleReset para que lo puedas probar visualmente?
Buena pregunta, Jean. Vamos al grano:
Java Swing es un toolkit (conjunto de librerías) para construir interfaces gráficas (GUI) en Java. Es parte de Java SE (estándar), y permite crear ventanas, botones, menús, tablas, etc.
Fue el sucesor de AWT (Abstract Window Toolkit), y por mucho tiempo fue la opción estándar para crear GUIs en Java... hasta que apareció JavaFX.
No necesitas ni deberías usar Swing.
JavaFX es más moderno, eficiente y con mejor soporte gráfico (animaciones, CSS, FXML, etc.).
Swing no se integra naturalmente con FXML, ni con AnimationTimer, ni con Platform.runLater() de JavaFX.
Si mezclas Swing con JavaFX, puedes tener errores de concurrencia y renderizado porque usan diferentes hilos de UI (Swing EDT vs JavaFX Application Thread).
Solo en casos como:
Proyectos viejos que ya lo usan.
Herramientas internas sin requerimientos gráficos complejos.
Simulaciones sencillas que no necesitan renderizado fluido.
Aplicaciones tipo formulario o utilitario rápido.
| Java Swing | JavaFX | |
|---|---|---|
| ¿Obsoleto? | En desuso, pero aún funcional | Moderno y activo |
| ¿FXML y SceneBuilder? | ❌ No | ✅ Sí |
| ¿Animaciones? | Muy limitadas | Nativas y eficientes |
| ¿Estilo CSS? | Limitado (UIManager) | Estilo real con CSS |
| ¿Este proyecto? | ❌ No usar | ✅ Solo JavaFX (ya estás usándolo) |
NO necesitas Java Swing.
Tu simulación debe usar solo JavaFX + FXML, que ya tienes bien configurado.
¿Quieres que te muestre cómo se vería una versión de este proyecto si fuera hecha en Swing para entender la diferencia visualmente?
Perfecto Jean, te explico todo paso a paso:
padding en JavaFX?En JavaFX (como en CSS o HTML), padding es el espacio interno entre el borde de un contenedor y su contenido.
text+----------------------------+ ← límite del `HBox` | padding | | [ Botón Botón Botón ] | ← contenido con espacio interior +----------------------------+
En tu FXML:
xml<padding>
<Insets top="10" right="10" bottom="10" left="10"/>
</padding>
Significa:
10 píxeles de espacio arriba
10 a la derecha
10 abajo
10 a la izquierda
Esto le da aire al contenido (los botones) dentro del HBox inferior.
Tú ya tienes un Pane llamado lienzo en el <center>. Es ahí donde dibujaremos.
Vamos a agregar un Rectangle como si fuera una carretera.
Le daremos color y posición centrada.
<center> con una carretera:xml<center>
<Pane fx:id="lienzo" prefWidth="800" prefHeight="600" style="-fx-background-color: lightgray;">
<children>
<Rectangle x="350" y="0" width="100" height="600" fill="gray" />
</children>
</Pane>
</center>
x="350": la carretera comienza 350 píxeles desde la izquierda (centrado aproximadamente en 800 px de ancho).
width="100": ancho de la carretera.
height="600": ocupa todo el alto del lienzo.
fill="gray": color gris asfalto.
xml<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns:fx="http://javafx.com/fxml"
fx:controller="edu.pucmm.controller.SimulationController">
<top>
<HBox>
<Button text="Salir" onAction="#handleExit"/>
</HBox>
</top>
<center>
<Pane fx:id="lienzo" prefWidth="800" prefHeight="600" style="-fx-background-color: lightgray;">
<children>
<!-- Carretera vertical en el centro -->
<Rectangle x="350" y="0" width="100" height="600" fill="gray" />
</children>
</Pane>
</center>
<bottom>
<HBox alignment="center" spacing="10">
<padding>
<Insets top="10" right="10" bottom="10" left="10"/>
</padding>
<Button text="Iniciar" onAction="#handlePlay"/>
<Button text="Pausar" onAction="#handlePause"/>
<Button text="Resetear" onAction="#handleReset"/>
</HBox>
</bottom>
</BorderPane>
¿Quieres que agregue también:
líneas blancas de carril sobre la carretera,
semáforos,
o la vista del cruce en forma de + (cuatro calles)?
Dímelo y lo ajustamos visualmente.
¡Perfecto Jean! Ya tienes una carretera horizontal con líneas amarillas. Ahora vamos a cruzarla con una carretera vertical, también con sus propias líneas, para formar una intersección tipo “+”.
Carretera horizontal:
Ya está: y=250, height=90
Carretera vertical:
Será: x=355, width=90
Líneas blancas horizontales y verticales (tipo paso peatonal o marcas de carril)
<children> existente:xml<!-- Carretera vertical que cruza -->
<Rectangle x="355" y="0" width="90" height="600" fill="gray" />
<!-- Líneas horizontales amarillas (ya tienes) -->
<!-- Líneas verticales blancas en la carretera vertical -->
<Rectangle x="395" y="10" width="5" height="30" fill="white" />
<Rectangle x="395" y="60" width="5" height="30" fill="white" />
<Rectangle x="395" y="110" width="5" height="30" fill="white" />
<Rectangle x="395" y="160" width="5" height="30" fill="white" />
<Rectangle x="395" y="210" width="5" height="30" fill="white" />
<Rectangle x="395" y="260" width="5" height="30" fill="white" />
<Rectangle x="395" y="310" width="5" height="30" fill="white" />
<Rectangle x="395" y="360" width="5" height="30" fill="white" />
<Rectangle x="395" y="410" width="5" height="30" fill="white" />
<Rectangle x="395" y="460" width="5" height="30" fill="white" />
<Rectangle x="395" y="510" width="5" height="30" fill="white" />
<Rectangle x="395" y="560" width="5" height="30" fill="white" />
Esto crea una intersección en forma de cruz, con:
Una carretera gris horizontal (y=250)
Una carretera gris vertical que la cruza en el centro (x=355)
Líneas decorativas en ambos ejes
<children> completo:xml<children>
<!-- Horizontal -->
<Rectangle x="0" y="250" width="800" height="90" fill="gray" />
<Rectangle x="10" y="292" width="30" height="5" fill="yellow" />
<!-- ... más líneas horizontales ... -->
<Rectangle x="760" y="292" width="30" height="5" fill="yellow" />
<!-- Vertical -->
<Rectangle x="355" y="0" width="90" height="600" fill="gray" />
<Rectangle x="395" y="10" width="5" height="30" fill="white" />
<Rectangle x="395" y="60" width="5" height="30" fill="white" />
<Rectangle x="395" y="110" width="5" height="30" fill="white" />
<Rectangle x="395" y="160" width="5" height="30" fill="white" />
<Rectangle x="395" y="210" width="5" height="30" fill="white" />
<Rectangle x="395" y="260" width="5" height="30" fill="white" />
<Rectangle x="395" y="310" width="5" height="30" fill="white" />
<Rectangle x="395" y="360" width="5" height="30" fill="white" />
<Rectangle x="395" y="410" width="5" height="30" fill="white" />
<Rectangle x="395" y="460" width="5" height="30" fill="white" />
<Rectangle x="395" y="510" width="5" height="30" fill="white" />
<Rectangle x="395" y="560" width="5" height="30" fill="white" />
</children>
¿Quieres que añadamos también los cuatro semáforos en las esquinas de la intersección? También puedo ayudarte a preparar los nodos que se actualizarán dinámicamente por la simulación.
¡Excelente trabajo, Jean! 🎉 Ya tienes:
✅ Una interfaz JavaFX funcional
✅ La intersección tipo "+" bien dibujada con marcas viales
✅ Botones de control (Iniciar, Pausar, Resetear)
✅ El sistema renderizando vehículos en movimiento
Ahora te detallo qué te falta como frontend (Jean) según las tareas asignadas en el documento:
| Tarea | Estado |
|---|---|
5.1 Diseño FXML base | ✅ |
5.3 Vista Escenario 1 - Cruce | ✅ |
5.5 Motor de renderizado (AnimationTimer) | ✅ (si los carros se mueven fluidamente) |
5.2 – Controlador principal SimulationControllerDebes tener métodos en tu controlador para:
handlePlay() → iniciar simulación
handlePause() → pausar
handleReset() → reiniciar
Ejemplo de estructura:
java@FXML
private void handlePlay() {
viewModel.iniciarSimulacion();
}
@FXML
private void handlePause() {
viewModel.pausarSimulacion();
}
@FXML
private void handleReset() {
viewModel.resetearSimulacion();
}
5.4 – Vista Escenario 2 (Autopista con Semáforos) 🔴🟡🟢Crea otro diseño main_autopista.fxml con:
Carriles ida y vuelta
Semáforos (rectángulos rojos/amarillos/verdes)
Botón para cambiar de escenario
💡 Esto se puede activar al hacer clic en “Iniciar” o tener un menú de selección de escenario.
5.6 – SimulationViewModelEncapsula la lógica de sincronización UI ↔ backend:
Suscribirse al SimulationModel
Llamar a Platform.runLater() si necesitas actualizar la UI fuera del AnimationTimer
Exponer una ObservableList<Node> (si lo necesitas para binding)
5.7 – Alertas y métricas UIEjemplo: cuando aparece un vehículo de emergencia:
javaPlatform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.WARNING, "¡Vehículo de emergencia en camino!");
alert.show();
});
Número de vehículos activos
FPS (si quieres medirlo)
Tiempo transcurrido de simulación
Puedes colocarlo en un VBox a la derecha o como una Label inferior.
8.1 - 8.4 – Documentación final 📄README.md con instrucciones de compilación (./gradlew run)
Capturas como la que acabas de subir
Informe técnico (estructura + screenshots + explicación MVVM + JavaFX)
Guía de usuario con controles y uso
👉 Implementar el SimulationController completo, y verificar que los botones hagan lo que deben (al menos disparar eventos).
¿Quieres que preparemos juntos el SimulationController.java base con estructura y conexiones al modelo?
Perfecto, Jean. Lo que tienes hasta ahora está muy bien estructurado y funcional para el escenario de prueba. Has logrado:
| Componente | Estado |
|---|---|
SimulationModel | ✔️ Maneja vehículos, notifica observadores, simula movimiento falso |
SimulationController | ✔️ Controla el ciclo (play/stop/reset) y dispara la simulación falsa |
simulacionFalsa() | ✔️ Emula movimiento de 2 vehículos en dirección contraria |
| JavaFX UI | ✔️ Visualización del cruce y vehículos en movimiento |
Tu método simulacionFalsa() simula dos vehículos:
Uno tipo normal, moviéndose de derecha a izquierda.
Uno tipo emergencia, de izquierda a derecha.
Ambos son renderizados en posiciones y = 270 y y = 320, simulando carriles paralelos.
Este enfoque es ideal para probar la vista y renderizado, incluso sin tener aún el backend real de Carlos.
Aquí algunas sugerencias para ir al siguiente nivel:
Agrega vehículos que bajen desde arriba o suban desde abajo:
javadouble x = 370 + (i * 50);
for (int y = 0; y < 100; y++) {
double posY = i % 2 == 0 ? 0 + y * 4 : 600 - (y * 4);
updateState(new VehiculoState("v" + i, x, posY, tipo));
Thread.sleep(30);
}
En tu Pane, puedes agregar:
javaRectangle semaforo = new Rectangle(385, 260, 10, 10);
semaforo.setFill(Color.RED);
Platform.runLater(() -> lienzo.getChildren().add(semaforo));
Puedes alternar setFill(Color.GREEN) para visualizar cambio.
En tu SimulationViewModel, dentro del onStateChange(...), puedes detectar esto:
javaif (estado.tipo() == TipoVehiculo.emergencia) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.WARNING, "¡Vehículo de emergencia!");
alert.show();
});
}
En el SimulationController, puedes tener:
java@FXML private Label contadorVehiculos;
@FXML
public void initialize() {
...
modelo.addObserver(snapshot -> {
Platform.runLater(() -> {
contadorVehiculos.setText("Vehículos activos: " + snapshot.size());
});
});
}
Y en el FXML agregas el <Label fx:id="contadorVehiculos"/>.
Ya puedes mostrar vehículos, los renderizas con JavaFX, y tienes todo listo para:
Agregar otros escenarios (Autopista)
Integrar con la lógica de Carlos (cuando esté lista)
Incluir métricas de UI, alertas, FPS, etc.
¿Quieres que ahora te ayude a implementar el SimulationViewModel para organizar mejor el renderizado y canalizar eventos de la UI?
¡Excelente idea, Jean! Para colocar cuatro semáforos en cada intersección de tu cruce (arriba, abajo, izquierda, derecha), puedes hacerlo de forma visual con Rectangle o Circle en el Pane, y luego más adelante puedes animarlos (cambiar color, etc.).
Crear 4 semáforos visuales con posición fija, uno por intersección del cruce.
FXMLSi usas FXML, puedes colocarlos dentro del <Pane fx:id="lienzo"> así:
xml<Pane fx:id="lienzo" prefWidth="800" prefHeight="600" style="-fx-background-color: lightgray;">
<children>
<!-- Calle horizontal y vertical -->
<!-- (Tu código de las calles aquí) -->
<!-- Semáforo arriba -->
<Circle fx:id="semaforoArriba" centerX="400" centerY="200" radius="10" fill="red"/>
<!-- Semáforo abajo -->
<Circle fx:id="semaforoAbajo" centerX="400" centerY="400" radius="10" fill="red"/>
<!-- Semáforo izquierda -->
<Circle fx:id="semaforoIzquierda" centerX="300" centerY="300" radius="10" fill="red"/>
<!-- Semáforo derecha -->
<Circle fx:id="semaforoDerecha" centerX="500" centerY="300" radius="10" fill="red"/>
</children>
</Pane>
En SimulationController.java:
javaimport javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
@FXML:java@FXML private Circle semaforoArriba;
@FXML private Circle semaforoAbajo;
@FXML private Circle semaforoIzquierda;
@FXML private Circle semaforoDerecha;
Para encender uno en verde (ej. el de arriba) y los demás en rojo:
javapublic void cambiarSemaforos(String direccionVerde) {
semaforoArriba.setFill(direccionVerde.equals("arriba") ? Color.LIMEGREEN : Color.RED);
semaforoAbajo.setFill(direccionVerde.equals("abajo") ? Color.LIMEGREEN : Color.RED);
semaforoIzquierda.setFill(direccionVerde.equals("izquierda") ? Color.LIMEGREEN : Color.RED);
semaforoDerecha.setFill(direccionVerde.equals("derecha") ? Color.LIMEGREEN : Color.RED);
}
Puedes llamarlo en handlePlay() para simular:
javacambiarSemaforos("arriba");
Cuando conectes con el SemaforoManager del backend, puedes:
Notificar desde Carlos → SimulationModel → SimulationObserver → ViewModel.
Luego llamar a cambiarSemaforos(...) según corresponda.
¿Quieres que te ayude a hacer que cambien de color automáticamente en un ciclo (ej. cada 5 segundos)?
Sí Jean, tu interfaz gráfica cumple con el escenario 1 del cruce de calles con 4 intersecciones a nivel visual. Te explico por partes y te indico si falta algo:
Tienes una calle horizontal y una vertical, cada una con líneas amarillas segmentadas: ✅ correcto.
Esto representa 4 posibles entradas/salidas: norte, sur, este y oeste → una intersección central.
Añadiste Circle rojos (con fx:id) en cada dirección cardinal (arriba, abajo, izquierda, derecha): ✅ correcto.
Aunque visualmente tienes una única gran intersección, el enunciado dice:
Escenario 1: Cruce de Calles con 4 Intersecciones
Cada intersección tiene un "pare".
Esto no significa 4 calles, sino 4 zonas de conflicto separadas que debes controlar individualmente. Por tanto:
Simular las 4 intersecciones independientes en lógica, no en visual.
Puedes imaginar el cruce como dividido en 4 cuadrantes: 🔲🔲🔲🔲
Cada cuadrante debe tener su propio Semaphore o lógica de control.
No. Visualmente estás bien.
Tu diseño representa perfectamente el cruce de 2 calles principales y cumple con:
Calles bien dibujadas.
Líneas centrales amarillas.
4 semáforos bien posicionados.
Visual:
Si quieres más realismo, podrías dibujar señales de "PARE" en cada esquina (opcional).
Lógica (Carlos):
Carlos debe dividir la intersección en 4 zonas y usar Semaphore(1, true) por cada una.
Tú no tienes que hacer eso, pero sí dibujar visualmente los vehículos cuando se mueven respetando esas zonas (ya lo haces).
Tu parte pendiente:
Agregar los estados visuales de los semáforos (verde, rojo, etc.).
Mostrar alerta o notificación si viene un vehículo de emergencia (Platform.runLater con Alert).
Mostrar contador de vehículos activos o tiempo (en la parte inferior o lateral).
Implementar el SimulationViewModel si aún no lo hiciste (para conectar bien con SimulationModel).
¿Te gustaría que te ayude con alguna de esas partes que te faltan implementar ahora?
Perfecto Jean. Vamos a desmenuzar y explicar en detalle tu código FXML y cómo funciona en JavaFX, especialmente cómo se conecta con el controlador y el SimulationModel. Este análisis te va a servir tanto para entender lo que tienes como para saber cómo extenderlo.
Es un archivo de definición de interfaz gráfica en JavaFX, similar a cómo HTML describe una página web.
Este FXML define una ventana principal (BorderPane) con 3 secciones:
Top (barra de salida)
Center (el área de simulación con las calles)
Bottom (botones de control: Iniciar, Pausar, Resetear)
xml<BorderPane xmlns:fx="http://javafx.com/fxml"
fx:controller="edu.pucmm.controller.SimulationController">
BorderPane → Layout de 5 regiones (top, bottom, center, left, right).
fx:controller → Vincula este FXML con tu clase Java llamada SimulationController.
<top>xml<HBox>
<Button text="Salir" onAction="#handleExit"/>
</HBox>
Un contenedor HBox (layout horizontal) que contiene un botón de salida.
El botón dispara el método handleExit() en tu SimulationController.
<center>: Simulación Visualxml<Pane fx:id="lienzo" prefWidth="800" prefHeight="600" style="-fx-background-color: lightgray;">
<children>
<!-- Calles y Semáforos -->
</children>
</Pane>
Pane se usa porque permite posicionamiento absoluto (x, y) → perfecto para simulaciones 2D.
fx:id="lienzo" → Es un identificador que se enlaza al atributo @FXML Pane lienzo en el controlador.
Dentro tiene:
Calle horizontal: un Rectangle gris con líneas amarillas.
Calle vertical: otro Rectangle + líneas amarillas.
4 semáforos (Circle) con fx:id: arriba, abajo, izquierda y derecha.
👉 Estos semáforos son elementos visuales que puedes cambiar de color dinámicamente desde Java, usando @FXML Circle semaforoArriba, etc.
<bottom>: Botones de Controlxml<HBox alignment="center" spacing="10">
<padding>
<Insets top="10" right="10" bottom="10" left="10"/>
</padding>
<Button text="Iniciar" onAction="#handlePlay"/>
<Button text="Pausar" onAction="#handlePause"/>
<Button text="Resetear" onAction="#handleReset"/>
</HBox>
HBox con 10px de espacio entre botones y padding de 10 en todos los lados.
Cada botón llama un método Java con onAction="#..." que está definido en tu controlador SimulationController.
javafx:controller="edu.pucmm.controller.SimulationController"
Esto indica que el FXML está conectado con esta clase Java:
javapublic class SimulationController {
@FXML private Pane lienzo;
@FXML public void handlePlay() { ... }
@FXML public void handlePause() { ... }
@FXML public void handleReset() { ... }
@FXML public void handleExit() { ... }
}
Por ejemplo:
java@FXML private Pane lienzo;
→ Se vincula automáticamente con este bloque en el FXML:
xml<Pane fx:id="lienzo" ... />
Ahora puedes agregar/quitar/cambiar nodos dentro del lienzo desde código Java.
Desde tu clase Main.java:
javaFXMLLoader loader = new FXMLLoader(getClass().getResource("/view/main.fxml"));
Scene scene = new Scene(loader.load());
primaryStage.setScene(scene);
Esto:
Carga el archivo FXML.
Crea la escena con el layout que definiste.
Muestra la ventana (primaryStage.show()).
Cambiar color de los semáforos:
Desde tu controlador puedes hacer:
java@FXML private Circle semaforoArriba;
// cambiar a verde
semaforoArriba.setFill(Color.GREEN);
Agregar vehículos visualmente:
En SimulationViewModel, puedes agregar Circle o Rectangle al lienzo para cada VehiculoState.
Animar la simulación:
Ya tienes un AnimationTimer que se ejecuta 60 veces por segundo y se encarga de renderizar los vehículos.
Mostrar alertas:
Si llega un vehículo de emergencia:
javaPlatform.runLater(() -> {
new Alert(Alert.AlertType.WARNING, "¡Vehículo de emergencia!").show();
});
| Parte | Rol |
|---|---|
| FXML | Define la estructura visual (como HTML) |
| fx:controller | Vincula el FXML con una clase Java |
| fx:id | Asocia un componente visual a una variable @FXML en Java |
| onAction | Ejecuta un método Java al hacer clic en un botón |
Pane lienzo | Es el espacio donde dibujas los vehículos y calles dinámicamente |
AnimationTimer | Llama a render() constantemente para actualizar la animación |
¿Quieres que ahora conectemos los Circle de semáforos con una clase que los actualice automáticamente en rojo/verde? ¿O prefieres avanzar con otro aspecto del frontend?
Excelente trabajo en equipo, Jean. El reporte de tu compañero Carlos indica que ya tienen todo el backend 100% funcional: vehículos como hilos (Runnable), con lógica de movimiento, reglas de tráfico, colisión y prioridad de emergencia. Ahora, tú como frontend (JavaFX) puedes avanzar conectando esa lógica visualmente.
Aquí te explico qué ya tienes, qué falta, y cómo integrarte ahora mismo en pasos concretos.
De tu lado, ya lograste:
Pane central (lienzo) donde se renderizan vehículos.
Calles dibujadas en horizontal y vertical.
Semáforos representados como Circle.
Botones: Iniciar, Pausar, Resetear.
SimulationController)AnimationTimer activo a 60 FPS.
Lógica para empezar simulación falsa.
Renderización básica con SimulationViewModel.
Carlos ya implementó:
Vehículo como Runnable, con lógica real.
Clases VehiculoNormal, VehiculoEmergencia.
VehiculoFactory: genera 10+ hilos listos.
SimulationModelAdapter: conecta lógica con la UI.
Pruebas automáticas y demo backend funcional (main()).
Ya no necesitas usar simulacionFalsa().
✅ Cambia esto:
javamodelo.simulacionFalsa();
🔁 Por esto:
javavehiculos = VehiculoFactory.createFleet(10, 0.3, adapter);
for (Vehiculo v : vehiculos) new Thread(v).start();
Pero antes, necesitas:
importar VehiculoFactory, Vehiculo, VehiculoNormal, VehiculoEmergencia.
implementar una clase tipo SimulationModelAdapter en tu lado para usar el modelo correctamente.
Tu SimulationController ya tiene botones. Conecta esos botones a:
javafor (Vehiculo v : vehiculos) v.pause();
for (Vehiculo v : vehiculos) v.resume();
for (Vehiculo v : vehiculos) v.stop();
Actualmente estás renderizando en render() dentro de SimulationViewModel.
Allí puedes usar:
javaCircle c = new Circle(5);
c.setFill(Color.BLUE);
c.setTranslateX(state.posX());
c.setTranslateY(state.posY());
🔁 Cambia color dependiendo de si es de emergencia o no:
javac.setFill(state.tipo() == TipoVehiculo.emergencia ? Color.RED : Color.BLUE);
Y usa Map<String, Node> para evitar duplicados.
Desde la clase Semaforo del backend, podrías:
Obtener estado actual (ROJO, VERDE, AMBAR).
Enviar notificación a UI usando Platform.runLater().
Cambiar el color del Circle correspondiente:
javasemaforoIzquierda.setFill(Color.GREEN); // o rojo, amarillo
VehiculoFactory, Vehiculo, etc., a tu módulo JavaFXAgrega en tu SimulationController:
javaprivate Vehiculo[] vehiculos;
SimulationModelAdapterjavaprivate static class Adapter implements ISimulationModel {
private final SimulationModel model;
public Adapter(SimulationModel model) {
this.model = model;
}
@Override
public void publishState(VehiculoState estado) {
model.updateState(estado);
}
@Override
public boolean puedeAvanzar(String id, double nextX, double nextY) {
return model.getVehiculos().values().stream()
.filter(v -> !v.id().equals(id))
.noneMatch(v -> Math.hypot(v.posX() - nextX, v.posY() - nextY) < 5);
}
}
handlePlay()java@FXML public void handlePlay() {
timer.start();
Adapter adapter = new Adapter(modelo);
vehiculos = VehiculoFactory.createFleet(10, 0.2, adapter);
for (Vehiculo v : vehiculos) new Thread(v).start();
}
java@FXML public void handlePause() {
timer.stop();
for (Vehiculo v : vehiculos) v.pause();
}
@FXML public void handleReset() {
timer.stop();
for (Vehiculo v : vehiculos) v.stop();
modelo.clear();
lienzo.getChildren().clear();
}
✔️ Simulación visual conectada al backend real.
✔️ Vehículos con animación y comportamiento real.
✔️ Colores por tipo de vehículo.
✔️ Botones funcionales para control.
✔️ (Opcional) Semáforos visuales dinámicos.
✔️ (Opcional) Muestra de alertas si hay emergencia:
javaif (vehiculo.tipo() == TipoVehiculo.emergencia)
Platform.runLater(() -> new Alert(AlertType.WARNING, "¡Emergencia!").show());
| Parte | Estado |
|---|---|
| Interfaz con FXML | ✅ Completa |
| Animación con JavaFX | ✅ Completa |
| Simulación falsa | ✅ Lista |
| Conexión con backend real | 🔄 En proceso (siguiendo estos pasos) |
| Manejo visual de semáforos | 🔄 Opcional |
| Documentación final UI | 🔲 Pendiente |
¿Quieres que te prepare el SimulationModelAdapter, SimulationController y FXML final ya listos para pegar? ¿O deseas continuar tú con base en esto?
¡Perfecto, Jean! Ya tienes la interfaz gráfica completamente funcional con selección de salida, tipo de vehículo y dirección. Ahora el siguiente paso es integrar la lógica del Escenario 1 hecha por tu compañero (Carlos) con tu vista y controlador JavaFX. Aquí te explico paso a paso lo que debes hacer para juntar ambos mundos:
CruceSimulationModel en vez del SimulationModelTu controlador usa:
javaprivate SimulationModel modelo;
Debes reemplazarlo por el nuevo modelo inteligente:
javaprivate CruceSimulationModel modelo;
Y en initialize():
javamodelo = new CruceSimulationModel(); // usa el modelo completo del cruce
Este modelo ya incluye control de intersecciones, prioridad, colisiones y observadores.
Usa la VehiculoFactory para crear el vehículo con sus parámetros y publicarlo al modelo. En crearVehiculo(), luego de mostrar el círculo, agrega:
javaVehiculo nuevoVehiculo = VehiculoFactory.createVehiculoUnico(
tipoVehiculoSeleccionado,
salidaSeleccionada,
direccionSeleccionada,
modelo // el ISimulationModel
);
new Thread(nuevoVehiculo).start();
Importante: asegúrate de que VehiculoFactory.createVehiculoUnico(...) existe. Si no, puedes adaptarlo desde createFleet(...).
Agrega una función en el controlador que actualice visualmente los vehículos cuando cambian:
javamodelo.addObserver(snapshot -> Platform.runLater(() -> {
// limpia los círculos antiguos (excepto líneas y stops)
lienzo.getChildren().removeIf(node -> node instanceof Circle && node.getId() != null && node.getId().startsWith("vehiculo"));
for (VehiculoState estado : snapshot.values()) {
Circle c = new Circle(estado.posX(), estado.posY(), 8);
c.setFill(estado.tipo() == TipoVehiculo.emergencia ? Color.RED : Color.BLUE);
c.setId("vehiculo-" + estado.id()); // para distinguirlos
lienzo.getChildren().add(c);
}
}));
Carlos implementó control con métodos como vehiculo.pause(), vehiculo.resume(), vehiculo.stop().
Guarda una lista de vehículos activos:
javaprivate final List<Vehiculo> vehiculosActivos = new ArrayList<>();
Cada vez que crees uno:
javavehiculosActivos.add(nuevoVehiculo);
new Thread(nuevoVehiculo).start();
Y en los botones:
java@FXML public void handlePause() {
vehiculosActivos.forEach(Vehiculo::pause);
}
@FXML public void handlePlay() {
vehiculosActivos.forEach(Vehiculo::resume);
}
@FXML public void handleReset() {
vehiculosActivos.forEach(Vehiculo::stop);
vehiculosActivos.clear();
lienzo.getChildren().removeIf(n -> n instanceof Circle && n.getId() != null && n.getId().startsWith("vehiculo"));
modelo.clear();
}
Asegúrate de tener los siguientes paquetes de Carlos integrados en tu proyecto:
markdownedu.pucmm.simulation:
- Vehiculo.java
- VehiculoNormal.java
- VehiculoEmergencia.java
- VehiculoFactory.java
- CruceSimulationModel.java
- InterseccionManager.java
- ColisionDetector.java
- Direccion.java
- PuntoSalida.java
Y también las interfaces como ISimulationModel.
🎯 Tu rol visual se completa con lo siguiente:
FXML bien estructurado con intersección visible
Configuración de menú de vehículo
Sistema de creación de vehículos
Sistema de renderizado en tiempo real
Agregar visualización de semáforos y lógica futura si deseas (opcional)
Agregar estadísticas visuales: número de vehículos por intersección, tiempos, etc. (bonus)
¿Quieres que te ayude a implementar la integración del CruceSimulationModel ahora directamente en tu SimulationController.java?
Puedo darte el código listo.
¡Perfecto, Jean! Ya que tu Main.java está preparado para ejecutar distintos modos (como demo y cruce), puedes probar lo que hizo tu compañero Carlos con solo un comando desde terminal usando Gradle.
Tu compañero registró "cruce" como argumento para correr su demo del Escenario 1. Para probarlo, corre:
bash./gradlew run --args="cruce"
🔁 Esto ejecutará la clase:
javaedu.pucmm.simulation.CruceEscenario1Demo.main(...)
💡 Requisitos:
Asegúrate de que CruceEscenario1Demo.java exista y tenga un main() válido.
Verifica que esté bien importado en el proyecto y que compile sin errores.
También puedes ejecutar la demo básica con:
bash./gradlew run --args="demo"
Esto correrá:
javaedu.pucmm.simulation.VehiculoDemoSimple.main(...)
Cuando lo ejecutes deberías ver algo como:
lessejecutando demostración del escenario 1: cruce de calles...
[INFO] Vehículo VEH-0001 intentando entrar a intersección NORTE
[INFO] VEH-0001 cruzó la intersección
...
demostración del cruce completada, terminando programa...
Lo que hiciste tú (FXML, menú, creación visual, JavaFX) es perfecto para ser el modo visual interactivo.
La arquitectura de Main.java está excelente:
--args="demo" → solo consola
--args="cruce" → lógica avanzada
sin args → JavaFX GUI interactiva (tu parte)
Si no ves logs o el programa termina sin hacer nada:
Asegúrate de que estás en la carpeta correcta (primer_avance)
Corre con más logs para depurar:
bash./gradlew run --args="cruce" --info
¿Quieres que revise el contenido de CruceEscenario1Demo contigo para verificar que hace lo que se espera?
Excelente pregunta, Jean. La respuesta corta es sí, si estás integrando la simulación del Cruce Escenario 1 con tu interfaz JavaFX (pane/lienzo), necesitas alinear las coordenadas lógicas (posX, posY) que usan los vehículos con las coordenadas gráficas (pixeles) de tu Pane.
El sistema que creó Carlos trabaja con coordenadas abstractas (como vehiculoPosX = 100.0) que pueden representar unidades de una simulación lógica (metros, pasos, ticks, etc.).
Pero tu FXML usa pixeles absolutos. Por ejemplo:
xml<Rectangle x="355" y="0" width="90" height="600" fill="gray" />
Esa es la calle vertical en la pantalla real.
Asegúrate de que las posiciones iniciales de los vehículos y las intersecciones del CruceManager estén centradas con lo que tienes en JavaFX. Por ejemplo:
java// Coordenadas reales del centro del cruce en el pane:
CENTER_X = 400
CENTER_Y = 295
En CruceManager.java, asegúrate de que las intersecciones estén configuradas así:
javapublic enum DireccionCruce {
NORTE(400, 230),
SUR(400, 360),
ESTE(460, 295),
OESTE(340, 295);
}
🔁 Estos valores deben coincidir con tu FXML.
Si Carlos usó una escala lógica (por ejemplo, 1 unidad = 4 píxeles), puedes hacer la conversión en tiempo real al renderizar:
javadouble pantallaX = vehiculo.posX() * 4;
double pantallaY = vehiculo.posY() * 4;
Pero esta opción no es necesaria si ya están usando posiciones reales en píxeles, como parece estar ocurriendo ahora.
En CruceManager.determinarInterseccionMasCercana() debes confirmar que las coordenadas de cada intersección (NORTE, SUR, etc.) coinciden con las del cruce de calles en JavaFX.
En ColisionDetector, asegúrate de que la distancia mínima para colisiones (ej. 15.0 o 20.0) esté ajustada a tamaños visuales (los vehículos miden 20 píxeles aprox).
| Componente | Acción necesaria |
|---|---|
CruceManager.DireccionCruce | Ajustar posiciones a los centerX, centerY de tu cruce en FXML |
ColisionDetector | Verifica que las distancias sean en píxeles |
Vehículos (VehiculoNormal, etc.) | Asegura que las coordenadas iniciales estén dentro del pane JavaFX |
Visualización (render() en ViewModel) | Dibuja en el Pane directamente en pixeles, sin escalas lógicas |
¿Quieres que revise contigo el archivo CruceManager y vea si las coordenadas ya están correctas o necesitan cambios para alinearse con tu GUI?
¡Perfecto, Jean! Gracias por compartir CruceManager.java. Vamos al punto clave de tu pregunta:
¿Tengo que cambiar los valores de
DireccionCrucea los píxeles reales de la pantalla?
Sí. Para que el método determinarInterseccionMasCercana() funcione correctamente con tu GUI, necesitas actualizar los valores posX y posY de las intersecciones (NORTE, SUR, ESTE, OESTE) a las coordenadas reales en píxeles que tienes en tu FXML.
javaNORTE(0, -50), // entrada desde arriba
SUR(0, 50), // entrada desde abajo
ESTE(50, 0), // entrada desde derecha
OESTE(-50, 0); // entrada desde izquierda
Estos valores son abstractos o lógicos, no representan coordenadas del Pane JavaFX.
Según tu diseño FXML del cruce (por ejemplo, el centro visual está en X ≈ 400, Y ≈ 295), aquí tienes una versión realista basada en tu GUI:
javaNORTE(400, 230), // arriba del cruce
SUR(400, 360), // abajo del cruce
ESTE(460, 295), // derecha del cruce
OESTE(340, 295); // izquierda del cruce
Estos valores:
Están centrados respecto a la carretera vertical (X = 395–445) y la horizontal (Y = 250–340).
Coinciden con tus stops y rectángulos de líneas.
Permiten a determinarInterseccionMasCercana() funcionar correctamente en pantalla real.
Imprime en consola cada solicitud de cruce:
javalogger.info("🚦 vehículo " + vehiculoId + " solicita cruzar desde " + direccionEntrada);
Y mueve visualmente un vehículo (desde crearVehiculo() en tu SimulationController) cerca de la intersección. Si la detección ocurre, ¡todo va bien!
Podrías mover esas coordenadas reales a una clase central, tipo:
javapublic final class CoordenadasInterseccion {
public static final Point2D NORTE = new Point2D(400, 230);
public static final Point2D SUR = new Point2D(400, 360);
public static final Point2D ESTE = new Point2D(460, 295);
public static final Point2D OESTE = new Point2D(340, 295);
}
Y usarlas así:
javaNORTE(CoordenadasInterseccion.NORTE.getX(), CoordenadasInterseccion.NORTE.getY())
| Tarea | Acción |
|---|---|
Actualizar posX, posY | Usa coordenadas reales del cruce en el Pane |
determinarInterseccionMasCercana | Así funcionará correctamente con GUI |
| Coordenadas sugeridas | NORTE(400, 230), SUR(400, 360), ESTE(460, 295), OESTE(340, 295) |
| Mejoras | Usa clase utilitaria para definir posiciones en un solo lugar |
¿Quieres que te ayude a editar esa enum con los nuevos valores exactos, o pasamos a cómo integrar esa lógica con tu controlador de GUI (SimulationController)?
¡Buena pregunta, Jean! Ahora que tienes CruceSimulationModel y CruceManager listos, integrar la lógica del cruce de intersecciones en tu SimulationController se reduce a 3 pasos clave:
CruceSimulationModel en lugar de SimulationModelEn SimulationController.java, cambia:
javaprivate SimulationModel modelo;
por:
javaprivate CruceSimulationModel modelo;
Y en el método initialize(), reemplaza:
javamodelo = new SimulationModel();
por:
javamodelo = new CruceSimulationModel(new SimulationModel(), "cruce-1");
Esto te conecta con la lógica completa del cruce y colisiones.
En el punto donde creas el vehículo en crearVehiculo(), una vez que empiece a moverse, debes hacer:
javaif (modelo.estaCercaDeInterseccion(vehiculoId, x, y)) {
modelo.solicitarCruceInterseccion(vehiculoId, tipo, x, y);
}
Este código puede ir dentro del ciclo de animación del vehículo (si lo estás moviendo tú), o si estás usando hilos tipo VehiculoNormal y VehiculoEmergencia, eso ya está contemplado allí (lo hizo tu compañero).
Si quieres que tu GUI se actualice con los vehículos que están cruzando y moviéndose:
En initialize(), suscríbete como observer:
javamodelo.getSimulationModel().addObserver(snapshot -> {
Platform.runLater(() -> {
lienzo.getChildren().removeIf(n -> n instanceof Circle && !"stop".equals(n.getId())); // limpiar solo vehículos
for (VehiculoState v : snapshot.values()) {
Circle vehiculo = new Circle(10, v.tipo() == TipoVehiculo.emergencia ? Color.RED : Color.BLUE);
vehiculo.setCenterX(v.posX());
vehiculo.setCenterY(v.posY());
lienzo.getChildren().add(vehiculo);
}
});
});
💡 Si tus vehículos están en hilos (Runnable), y publican su estado en CruceSimulationModel.publishState(), esto mantendrá la GUI sincronizada en tiempo real.
Actualiza tu handlePlay() para iniciar la simulación de verdad:
java@FXML public void handlePlay() {
Vehiculo[] vehiculos = VehiculoFactory.createFleet(10, 0.3, modelo);
for (Vehiculo v : vehiculos) {
new Thread(v).start();
}
}
Corre tu app con ./gradlew run.
Haz clic en "Iniciar".
Verás vehículos moverse.
En la consola deben imprimirse cosas como:
🚦 vehículo VEH-0001 solicita cruzar desde NORTE... ✅ vehículo VEH-0001 cruzó correctamente...
| Acción | Código o lugar |
|---|---|
Usar CruceSimulationModel | initialize() en SimulationController |
| Solicitar cruce si está cerca | Dentro del hilo del vehículo o lógica de movimiento |
| Mostrar vehículos en pantalla | Subscribirse como observer al modelo |
| Crear vehículos con factory | En handlePlay() |
¿Quieres que te escriba el initialize() y handlePlay() completos con estas mejoras?
Sí, hay que hacer algunos cambios en VehiculoFactory para que los vehículos funcionen correctamente dentro del cruce de calles con intersecciones reales, en coordenadas acordes al lienzo JavaFX (por ejemplo, 800x600).
El código actual genera posiciones aleatorias dentro de un rango 0.0 a 100.0:
javaprivate static final double MIN_POS = 0.0;
private static final double MAX_POS = 100.0;
🔴 Esto no corresponde con la interfaz visual, donde las posiciones reales están centradas alrededor de:
🟦 Horizontal: X ∈ [0, 800]
🟩 Vertical: Y ∈ [0, 600]
🟥 Cruce central: alrededor de (400, 300)
Para hacerlo bien, define las posiciones iniciales de cada entrada al cruce:
javaprivate static final double POS_ENTRADA_NORTE_X = 400;
private static final double POS_ENTRADA_NORTE_Y = 0;
private static final double POS_ENTRADA_SUR_X = 400;
private static final double POS_ENTRADA_SUR_Y = 600;
private static final double POS_ENTRADA_OESTE_X = 0;
private static final double POS_ENTRADA_OESTE_Y = 300;
private static final double POS_ENTRADA_ESTE_X = 800;
private static final double POS_ENTRADA_ESTE_Y = 300;
Luego, puedes adaptar createFleet(...) o crear un nuevo método más controlado para generar vehículos desde direcciones específicas.
javapublic static Vehiculo[] createCruceDemoFleet(ISimulationModel simulationModel) {
return new Vehiculo[] {
new VehiculoNormal("VEH-0001", POS_ENTRADA_NORTE_X, POS_ENTRADA_NORTE_Y, Direccion.recto, simulationModel),
new VehiculoNormal("VEH-0002", POS_ENTRADA_SUR_X, POS_ENTRADA_SUR_Y, Direccion.recto, simulationModel),
new VehiculoEmergencia("VEH-0003", POS_ENTRADA_OESTE_X, POS_ENTRADA_OESTE_Y, Direccion.recto, simulationModel),
new VehiculoNormal("VEH-0004", POS_ENTRADA_ESTE_X, POS_ENTRADA_ESTE_Y, Direccion.recto, simulationModel)
};
}
O, si prefieres mantener aleatoriedad, pero dentro del escenario:
javaprivate static double[] generarPosicionDesdeDireccion(DireccionCruce direccion) {
return switch (direccion) {
case NORTE -> new double[]{400, 0};
case SUR -> new double[]{400, 600};
case ESTE -> new double[]{800, 300};
case OESTE -> new double[]{0, 300};
};
}
Y en tu createRandomVehicle(...) puedes hacer:
javaDireccionCruce entrada = DireccionCruce.values()[ThreadLocalRandom.current().nextInt(4)];
double[] pos = generarPosicionDesdeDireccion(entrada);
Luego en tu SimulationController, simplemente invocas:
javaVehiculo[] vehiculos = VehiculoFactory.createCruceDemoFleet(modelo);
for (Vehiculo v : vehiculos) {
new Thread(v).start();
}
| Cambio | Qué hacer |
|---|---|
| Coordenadas | Usar valores realistas del lienzo (800x600) |
| Dirección inicial | Asignar según el lado del cruce |
| Factory actual | No usar generateRandomPosition(), usar métodos fijos |
| Crear demo específica | Crear método createCruceDemoFleet() para pruebas visuales |
¿Quieres que te genere el código completo actualizado del factory con el método nuevo createCruceDemoFleet() listo para usar?
Perfecto Jean, con esa explicación queda claro: los vehículos ya no se generan aleatoriamente, sino a partir de una configuración explícita elegida por el usuario desde el menú. Eso significa que debes ajustar el VehiculoFactory y eliminar toda la aleatoriedad innecesaria.
Solo mantén lo esencial para crear un vehículo a partir de los parámetros seleccionados por el usuario:
ID único
TipoVehiculo (normal, emergencia)
PuntoSalida (ARRIBA, ABAJO, etc.)
Direccion (recto, izquierda, derecha, vuelta_u)
Coordenadas iniciales (según el punto de salida)
VehiculoFactoryjavapublic static Vehiculo createConfiguredVehicle(
TipoVehiculo tipo,
PuntoSalida salida,
Direccion direccion,
ISimulationModel simulationModel) {
String id = generateVehicleId();
double[] coords = getStartingCoordinates(salida);
return switch (tipo) {
case normal -> new VehiculoNormal(id, coords[0], coords[1], direccion, simulationModel);
case emergencia -> new VehiculoEmergencia(id, coords[0], coords[1], direccion, simulationModel);
};
}
Y ahora define el método getStartingCoordinates:
javaprivate static double[] getStartingCoordinates(PuntoSalida salida) {
return switch (salida) {
case ARRIBA -> new double[]{400, 0};
case ABAJO -> new double[]{400, 600};
case IZQUIERDA -> new double[]{0, 300};
case DERECHA -> new double[]{800, 300};
};
}
También conserva el generador de IDs:
javaprivate static final AtomicInteger vehicleIdCounter = new AtomicInteger(1);
private static String generateVehicleId() {
return "VEH-" + String.format("%04d", vehicleIdCounter.getAndIncrement());
}
SimulationController.javaCuando el usuario presiona "Crear Vehículo", en lugar de solo dibujar un Circle, ahora puedes:
javaVehiculo vehiculo = VehiculoFactory.createConfiguredVehicle(
tipoVehiculoSeleccionado,
salidaSeleccionada,
direccionSeleccionada,
cruceSimulationModel
);
new Thread(vehiculo).start();
Y si lo deseas, puedes seguir dibujando el Circle como visual inicial o simplemente dejar que el renderizador (como AnimationTimer) lo dibuje con base en los estados (VehiculoState).
Ya puedes eliminar los métodos:
createRandomVehicle
createFleet
createMultipleVehicles
generateRandomPosition
generateRandomDirection
generateRandomType
Solo necesitas el createConfiguredVehicle(...), y todo estará alineado con tu menú visual 👌
¿Te genero el código completo del nuevo VehiculoFactory limpio para que lo reemplaces?
Claro Jean, aquí tienes la clase VehiculoFactory completamente limpia, funcional y adaptada a tu simulación visual basada en selección manual desde el menú:
VehiculoFactory.javajavapackage edu.pucmm.simulation;
import edu.pucmm.model.PuntoSalida;
import edu.pucmm.model.TipoVehiculo;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Fábrica de vehículos basada en configuración explícita del usuario (sin aleatoriedad).
*/
public class VehiculoFactory {
private static final AtomicInteger vehicleIdCounter = new AtomicInteger(1);
/**
* Crea un vehículo basado en parámetros seleccionados por el usuario.
*
* @param tipo Tipo de vehículo (normal o emergencia)
* @param salida Punto de salida (ARRIBA, ABAJO, etc.)
* @param direccion Dirección deseada (recto, izquierda, derecha, vuelta_u)
* @param simulationModel Modelo de simulación (implementa ISimulationModel)
* @return Vehiculo Runnable listo para ejecutarse
*/
public static Vehiculo createConfiguredVehicle(
TipoVehiculo tipo,
PuntoSalida salida,
Direccion direccion,
ISimulationModel simulationModel) {
String id = generateVehicleId();
double[] coords = getStartingCoordinates(salida);
return switch (tipo) {
case normal -> new VehiculoNormal(id, coords[0], coords[1], direccion, simulationModel);
case emergencia -> new VehiculoEmergencia(id, coords[0], coords[1], direccion, simulationModel);
};
}
/**
* Genera un ID único para cada vehículo.
*/
private static String generateVehicleId() {
return "VEH-" + String.format("%04d", vehicleIdCounter.getAndIncrement());
}
/**
* Devuelve las coordenadas de inicio según el punto de salida.
*/
private static double[] getStartingCoordinates(PuntoSalida salida) {
return switch (salida) {
case ARRIBA -> new double[]{400, 0}; // Entra desde arriba
case ABAJO -> new double[]{400, 600}; // Entra desde abajo
case IZQUIERDA -> new double[]{0, 300}; // Entra desde izquierda
case DERECHA -> new double[]{800, 300}; // Entra desde derecha
};
}
/**
* Reinicia el contador de IDs (útil para pruebas).
*/
public static void resetIdCounter() {
vehicleIdCounter.set(1);
}
}
Ya puedes borrar sin miedo del archivo anterior:
createRandomVehicle(...)
createVehicle(...) con posición aleatoria
createFleet(...)
generateRandomPosition()
generateRandomDirection()
generateRandomType()
shuffleArray(...)
Con este nuevo VehiculoFactory, tu sistema queda 100% coherente con el flujo visual: selección → creación → ejecución en hilo → renderizado.
¿Te ayudo a ajustar tu SimulationController para que use este VehiculoFactory directo?
¡Buena observación, Jean! Ese método handlePlay() todavía está usando la lógica antigua de creación de flotas aleatorias (VehiculoFactory.createFleet(...)), que ya no aplica porque ahora los vehículos los configuras y creas tú manualmente desde la interfaz.
Modificar handlePlay() para iniciar solo los vehículos ya creados manualmente, que tú agregaste uno por uno con el botón "Crear Vehículo".
handlePlay():Primero, declara una lista de vehículos activos en tu SimulationController:
javaprivate final List<Vehiculo> vehiculosActivos = new ArrayList<>();
Luego cambia tu handlePlay() así:
java@FXML
public void handlePlay() {
for (Vehiculo vehiculo : vehiculosActivos) {
new Thread(vehiculo).start();
}
System.out.println("🚦 Simulación iniciada con " + vehiculosActivos.size() + " vehículos.");
}
crearVehiculo(), agrégalo a la lista vehiculosActivosEjemplo:
javaVehiculo vehiculo = VehiculoFactory.createConfiguredVehicle(
tipoVehiculoSeleccionado,
salidaSeleccionada,
direccionSeleccionada,
cruceSimulationModel // si ya integraste eso
);
vehiculosActivos.add(vehiculo);
Reemplaza createFleet(...) por lista manual vehiculosActivos.
Llama a new Thread(vehiculo).start() solo para los que tú creaste.
Esto permite simular casos específicos uno por uno (emergencia, dirección, origen, etc.).
¿Te gustaría que te ayude a implementar el botón "Detener todos" o "Reset" para que pare los hilos?
Excelente pregunta, Jean.
VehiculoDemoSimple en tu proyecto final?Respuesta corta:
👉 No, ya no es necesaria para la versión JavaFX interactiva.
VehiculoDemoSimple?VehiculoDemoSimple fue creada por tu compañero para hacer una demostración en consola, sin interfaz gráfica, de que los agentes (Vehiculo, VehiculoEmergencia, etc.) funcionaban correctamente:
Se crea una flota aleatoria.
Se ejecutan hilos.
Se pausan, reanudan y detienen.
Se muestran logs en consola.
No tiene nada visual (ni semáforos, ni stops, ni lógica de cruce real).
Podrías mantenerla por estos motivos:
Para pruebas automáticas en consola.
Para debugging unitario sin UI.
Como ejemplo claro de uso de los hilos de vehículos.
Puedes eliminarla si:
Tu proyecto solo usará JavaFX con interfaz.
Ya integraste la lógica de CruceSimulationModel, VehiculoFactory y SimulationModel en tu controlador (SimulationController).
Ya no necesitas validaciones en consola o logs sin GUI.
✅ Mi consejo:
Muévela a un paquete edu.pucmm.simulation.demo o edu.pucmm.util.testing por si acaso quieres hacer regresiones.
Y quita su referencia del Main.java, específicamente esta parte:
javacase "demo" -> {
VehiculoDemoSimple.main(new String[0]);
System.exit(0);
}
¿Quieres que te sugiera un reemplazo gráfico para las estadísticas finales (mostrarEstadisticasFinales(...)) usando JavaFX?
Sí Jean, ese bloque necesitas cambiarlo, porque ahora:
Ya no estás creando vehículos aleatoriamente.
Tú (como usuario en la GUI) decides:
🚦 Punto de salida (PuntoSalida)
🔄 Dirección (Direccion)
🚑 Tipo de vehículo (TipoVehiculo)
Además, los vehículos no se deben crear en bloque (createFleet(...)), sino uno por uno, con base en lo que seleccionaste en el menú.
Ya tienes estos datos listos desde el menú:
javaPuntoSalida salidaSeleccionada; TipoVehiculo tipoVehiculoSeleccionado; Direccion direccionSeleccionada;
Y también tienes acceso al ISimulationModel, que debería ser una instancia de CruceSimulationModel.
Entonces, en lugar de:
javaVehiculo[] vehiculos = VehiculoFactory.createFleet(5, 0.2, adapter);
for (Vehiculo v : vehiculos) {
new Thread(v).start();
}
Debes hacer esto en tu método crearVehiculo(), justo después de dibujar el círculo:
java// Adaptador para la lógica de cruce
ISimulationModel adapter = new CruceSimulationModel(modelo, "cruce-principal");
// Generar ID único
String id = "veh" + System.currentTimeMillis(); // o con un contador
// Posiciones iniciales en píxeles (ya las tienes cuando dibujas el círculo)
double posX = vehiculo.getCenterX();
double posY = vehiculo.getCenterY();
// Crear instancia del vehículo (según tipo)
Vehiculo vehiculoLogico = VehiculoFactory.createVehicle(
id,
tipoVehiculoSeleccionado,
posX,
posY,
direccionSeleccionada,
adapter
);
// Iniciar el hilo del vehículo
new Thread(vehiculoLogico).start();
Mantén esa lógica en un método reutilizable si planeas crear más vehículos luego. Por ejemplo:
javaprivate void lanzarVehiculoDesdeConfiguracionActual(double x, double y) {
...
}
¿Quieres que te dé esa lógica ya integrada en tu método crearVehiculo() para reemplazar el Circle con la lógica completa (gráfica + lógica)?
Perfect, Jean! So if you've already deleted the methodcreateFleet(...) , and now vehicle creation depends solely on the settings chosen in the GUI, then:
javaVehiculo[] vehiculos = VehiculoFactory.createFleet(...);
Because that function no longer exists and is no longer needed.
javaVehiculoFactory.createVehicle(
String id,
TipoVehiculo tipo,
double posX,
double posY,
Direccion direccion,
ISimulationModel simulationModel
);
You can do this directly within your crearVehiculo()del method SimulationController.
Here's how everything would look inside your crearVehiculo() unified UI + actual logic :
java@FXML
public void crearVehiculo() {
System.out.println("--- Creando Vehículo ---");
System.out.printf("Parámetros: Salida=%s, Tipo=%s, Dirección=%s\n",
salidaSeleccionada, tipoVehiculoSeleccionado, direccionSeleccionada);
// Validación
if (salidaSeleccionada == null || tipoVehiculoSeleccionado == null || direccionSeleccionada == null) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setTitle("Configuración Incompleta");
alert.setHeaderText(null);
alert.setContentText("Por favor, selecciona salida, tipo y dirección antes de crear un vehículo.");
alert.showAndWait();
return;
}
// Crear círculo visual
Circle vehiculoVisual = new Circle(10);
vehiculoVisual.setFill(tipoVehiculoSeleccionado == TipoVehiculo.emergencia ? Color.WHITE : Color.BLUE);
// Posiciones gráficas (también se usarán para lógica)
double x = 0, y = 0;
switch (salidaSeleccionada) {
case ARRIBA -> { x = 380; y = 15; }
case ABAJO -> { x = 420; y = 585; }
case IZQUIERDA -> { x = 15; y = 310; }
case DERECHA -> { x = 785; y = 280; }
}
vehiculoVisual.setCenterX(x);
vehiculoVisual.setCenterY(y);
lienzo.getChildren().add(vehiculoVisual);
// Crear vehículo lógico (agente Runnable)
String id = "veh" + System.currentTimeMillis();
ISimulationModel adapter = new CruceSimulationModel(modelo, "cruce-visual");
Vehiculo agente = VehiculoFactory.createVehicle(
id,
tipoVehiculoSeleccionado,
x, y,
direccionSeleccionada,
adapter
);
new Thread(agente).start();
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Vehículo Creado");
alert.setHeaderText(null);
alert.setContentText("Vehículo " + tipoVehiculoSeleccionado + " creado desde " + salidaSeleccionada);
alert.showAndWait();
}
With this change:
The method createFleet(...)is permanently removed.
Vehicle creation is based solely on what the user selects (point, type, direction).
Business logic and visualization are integrated .
Would you like me to prepare the VehiculoFactory.java clean one too , without createFleet, only with createVehicle(...)?